// @lint-ignore-every LICENSELINT // Copyright Epic Games, Inc. All Rights Reserved. #include "OculusXRHandTracking.h" #include "OculusXRHMD.h" #include "Misc/CoreDelegates.h" #include "IOculusXRInputModule.h" #include "Animation/Skeleton.h" #include "BoneWeights.h" #include "Components/SkeletalMeshComponent.h" #include "Rendering/SkeletalMeshLODRenderData.h" #include "Rendering/SkeletalMeshRenderData.h" #include "Rendering/SkeletalMeshLODModel.h" #include "Rendering/SkeletalMeshModel.h" #include "Materials/Material.h" #include "Materials/MaterialInstanceDynamic.h" #include "Engine/SkinnedAssetCommon.h" #include "Model.h" #include "MaterialDomain.h" #define OCULUS_TO_UE4_SCALE 100.0f namespace OculusXRInput { static FInputDeviceId GetDeviceID(int32 ControllerId) { IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get(); FPlatformUserId InPlatformUser = FGenericPlatformMisc::GetPlatformUserForUserIndex(ControllerId); FInputDeviceId InDeviceId = INPUTDEVICEID_NONE; DeviceMapper.RemapControllerIdToPlatformUserAndDevice(ControllerId, InPlatformUser, InDeviceId); return InDeviceId; } FQuat FOculusHandTracking::GetBoneRotation(const int32 ControllerIndex, const EOculusXRHandType DeviceHand, const EOculusXRBone BoneId) { FQuat Rotation = FQuat::Identity; if (BoneId <= EOculusXRBone::Invalid && BoneId >= EOculusXRBone::Bone_Max) { return Rotation; } #if OCULUS_INPUT_SUPPORTED_PLATFORMS TSharedPtr OculusXRInputModule = StaticCastSharedPtr(IOculusXRInputModule::Get().GetInputDevice()); if (OculusXRInputModule.IsValid()) { const FInputDeviceId InDeviceId = GetDeviceID(ControllerIndex); TArray ControllerPairs = OculusXRInputModule.Get()->ControllerPairs; for (const FOculusControllerPair& HandPair : ControllerPairs) { if (HandPair.DeviceId == InDeviceId) { if (DeviceHand != EOculusXRHandType::None) { ovrpHand Hand = DeviceHand == EOculusXRHandType::HandLeft ? ovrpHand_Left : ovrpHand_Right; const FOculusHandControllerState& HandState = HandPair.HandControllerStates[Hand]; int32 OvrBoneId = ToOvrBone(BoneId); Rotation = HandState.BoneRotations[OvrBoneId]; break; } } } } #endif return Rotation; } float FOculusHandTracking::GetHandScale(const int32 ControllerIndex, const EOculusXRHandType DeviceHand) { #if OCULUS_INPUT_SUPPORTED_PLATFORMS TSharedPtr OculusXRInputModule = StaticCastSharedPtr(IOculusXRInputModule::Get().GetInputDevice()); if (OculusXRInputModule.IsValid()) { const FInputDeviceId InDeviceId = GetDeviceID(ControllerIndex); TArray ControllerPairs = OculusXRInputModule.Get()->ControllerPairs; for (const FOculusControllerPair& HandPair : ControllerPairs) { if (HandPair.DeviceId == InDeviceId) { if (DeviceHand != EOculusXRHandType::None) { ovrpHand Hand = DeviceHand == EOculusXRHandType::HandLeft ? ovrpHand_Left : ovrpHand_Right; return HandPair.HandControllerStates[Hand].HandScale; } } } } #endif return 1.0f; } EOculusXRTrackingConfidence FOculusHandTracking::GetTrackingConfidence(const int32 ControllerIndex, const EOculusXRHandType DeviceHand) { #if OCULUS_INPUT_SUPPORTED_PLATFORMS TSharedPtr OculusXRInputModule = StaticCastSharedPtr(IOculusXRInputModule::Get().GetInputDevice()); if (OculusXRInputModule.IsValid()) { const FInputDeviceId InDeviceId = GetDeviceID(ControllerIndex); TArray ControllerPairs = OculusXRInputModule.Get()->ControllerPairs; for (const FOculusControllerPair& HandPair : ControllerPairs) { if (HandPair.DeviceId == InDeviceId) { if (DeviceHand != EOculusXRHandType::None) { ovrpHand Hand = DeviceHand == EOculusXRHandType::HandLeft ? ovrpHand_Left : ovrpHand_Right; return HandPair.HandControllerStates[Hand].TrackingConfidence; } } } } #endif return EOculusXRTrackingConfidence::Low; } EOculusXRTrackingConfidence FOculusHandTracking::GetFingerTrackingConfidence(const int32 ControllerIndex, const EOculusXRHandType DeviceHand, const EOculusHandAxes Finger) { TSharedPtr OculusXRInputModule = StaticCastSharedPtr(IOculusXRInputModule::Get().GetInputDevice()); if (OculusXRInputModule.IsValid()) { const FInputDeviceId InDeviceId = GetDeviceID(ControllerIndex); TArray ControllerPairs = OculusXRInputModule.Get()->ControllerPairs; for (const FOculusControllerPair& HandPair : ControllerPairs) { if (HandPair.DeviceId == InDeviceId) { if (DeviceHand != EOculusXRHandType::None) { ovrpHand Hand = DeviceHand == EOculusXRHandType::HandLeft ? ovrpHand_Left : ovrpHand_Right; return HandPair.HandControllerStates[Hand].FingerConfidences[(int)Finger]; } } } } return EOculusXRTrackingConfidence::Low; } FTransform FOculusHandTracking::GetPointerPose(const int32 ControllerIndex, const EOculusXRHandType DeviceHand, const float WorldToMeters) { #if OCULUS_INPUT_SUPPORTED_PLATFORMS TSharedPtr OculusXRInputModule = StaticCastSharedPtr(IOculusXRInputModule::Get().GetInputDevice()); if (OculusXRInputModule.IsValid()) { const FInputDeviceId InDeviceId = GetDeviceID(ControllerIndex); TArray ControllerPairs = OculusXRInputModule.Get()->ControllerPairs; for (const FOculusControllerPair& HandPair : ControllerPairs) { if (HandPair.DeviceId == InDeviceId) { if (DeviceHand != EOculusXRHandType::None) { ovrpHand Hand = DeviceHand == EOculusXRHandType::HandLeft ? ovrpHand_Left : ovrpHand_Right; FTransform PoseTransform = HandPair.HandControllerStates[Hand].PointerPose; PoseTransform.SetLocation(PoseTransform.GetLocation() * WorldToMeters); return PoseTransform; } } } } #endif return FTransform(); } bool FOculusHandTracking::IsPointerPoseValid(const int32 ControllerIndex, const EOculusXRHandType DeviceHand) { #if OCULUS_INPUT_SUPPORTED_PLATFORMS TSharedPtr OculusXRInputModule = StaticCastSharedPtr(IOculusXRInputModule::Get().GetInputDevice()); if (OculusXRInputModule.IsValid()) { const FInputDeviceId InDeviceId = GetDeviceID(ControllerIndex); TArray ControllerPairs = OculusXRInputModule.Get()->ControllerPairs; for (const FOculusControllerPair& HandPair : ControllerPairs) { if (HandPair.DeviceId == InDeviceId) { if (DeviceHand != EOculusXRHandType::None) { ovrpHand Hand = DeviceHand == EOculusXRHandType::HandLeft ? ovrpHand_Left : ovrpHand_Right; return HandPair.HandControllerStates[Hand].bIsPointerPoseValid; } } } } #endif return false; } bool FOculusHandTracking::IsHandTrackingEnabled() { #if OCULUS_INPUT_SUPPORTED_PLATFORMS ovrpBool result; FOculusXRHMDModule::GetPluginWrapper().GetHandTrackingEnabled(&result); return result == ovrpBool_True; #else return false; #endif } bool FOculusHandTracking::IsHandDominant(const int32 ControllerIndex, const EOculusXRHandType DeviceHand) { #if OCULUS_INPUT_SUPPORTED_PLATFORMS TSharedPtr OculusXRInputModule = StaticCastSharedPtr(IOculusXRInputModule::Get().GetInputDevice()); if (OculusXRInputModule.IsValid()) { const FInputDeviceId InDeviceId = GetDeviceID(ControllerIndex); TArray ControllerPairs = OculusXRInputModule.Get()->ControllerPairs; for (const FOculusControllerPair& HandPair : ControllerPairs) { if (HandPair.DeviceId == InDeviceId) { if (DeviceHand != EOculusXRHandType::None) { ovrpHand Hand = DeviceHand == EOculusXRHandType::HandLeft ? ovrpHand_Left : ovrpHand_Right; return HandPair.HandControllerStates[Hand].bIsDominantHand; } } } } #endif return false; } bool FOculusHandTracking::IsHandPositionValid(int32 ControllerIndex, EOculusXRHandType DeviceHand) { TSharedPtr OculusXRInputModule = StaticCastSharedPtr(IOculusXRInputModule::Get().GetInputDevice()); if (OculusXRInputModule.IsValid()) { const FInputDeviceId InDeviceId = GetDeviceID(ControllerIndex); TArray ControllerPairs = OculusXRInputModule.Get()->ControllerPairs; for (const FOculusControllerPair& HandPair : ControllerPairs) { if (HandPair.DeviceId == InDeviceId) { if (DeviceHand != EOculusXRHandType::None) { ovrpHand Hand = DeviceHand == EOculusXRHandType::HandLeft ? ovrpHand_Left : ovrpHand_Right; return HandPair.HandControllerStates[Hand].bIsPositionValid; } } } } return false; } bool FOculusHandTracking::GetHandSkeletalMesh(USkeletalMesh* HandSkeletalMesh, const EOculusXRHandType SkeletonType, const EOculusXRHandType MeshType, const float WorldToMeters) { #if OCULUS_INPUT_SUPPORTED_PLATFORMS if (HandSkeletalMesh) { ovrpMesh* OvrMesh = new ovrpMesh(); ovrpSkeleton2* OvrSkeleton = new ovrpSkeleton2(); ovrpSkeletonType OvrSkeletonType = (ovrpSkeletonType)((int32)SkeletonType - 1); ovrpMeshType OvrMeshType = (ovrpMeshType)((int32)MeshType - 1); ovrpResult SkelResult = FOculusXRHMDModule::GetPluginWrapper().GetSkeleton2(OvrSkeletonType, OvrSkeleton); ovrpResult MeshResult = FOculusXRHMDModule::GetPluginWrapper().GetMesh(OvrMeshType, OvrMesh); if (SkelResult != ovrpSuccess || MeshResult != ovrpSuccess) { #if !WITH_EDITOR UE_LOG(LogOcHandTracking, Error, TEXT("Failed to get mesh or skeleton data from Oculus runtime.")); #endif delete OvrMesh; delete OvrSkeleton; return false; } // Create Skeletal Mesh LOD Render Data #if WITH_EDITOR FSkeletalMeshLODModel* LodRenderData = new FSkeletalMeshLODModel(); HandSkeletalMesh->GetImportedModel()->LODModels.Add(LodRenderData); #else FSkeletalMeshLODRenderData* LodRenderData = new FSkeletalMeshLODRenderData(); HandSkeletalMesh->AllocateResourceForRendering(); HandSkeletalMesh->GetResourceForRendering()->LODRenderData.Add(LodRenderData); #endif // Set default LOD Info FSkeletalMeshLODInfo& LodInfo = HandSkeletalMesh->AddLODInfo(); LodInfo.ScreenSize = 0.3f; LodInfo.LODHysteresis = 0.2f; LodInfo.BuildSettings.bUseFullPrecisionUVs = true; InitializeHandSkeleton(HandSkeletalMesh, OvrSkeleton, WorldToMeters); // Add default material as backup LodInfo.LODMaterialMap.Add(0); UMaterialInterface* DefaultMaterial = UMaterial::GetDefaultMaterial(MD_Surface); HandSkeletalMesh->GetMaterials().Add(DefaultMaterial); HandSkeletalMesh->GetMaterials()[0].UVChannelData.bInitialized = true; // Set skeletal mesh properties HandSkeletalMesh->SetHasVertexColors(true); HandSkeletalMesh->SetHasBeenSimplified(false); HandSkeletalMesh->SetEnablePerPolyCollision(false); InitializeHandMesh(HandSkeletalMesh, OvrMesh, WorldToMeters); #if WITH_EDITOR HandSkeletalMesh->InvalidateDeriveDataCacheGUID(); HandSkeletalMesh->PostEditChange(); #endif // Create Skeleton object and merge all bones HandSkeletalMesh->SetSkeleton(NewObject()); HandSkeletalMesh->GetSkeleton()->MergeAllBonesToBoneTree(HandSkeletalMesh); HandSkeletalMesh->PostLoad(); delete OvrMesh; delete OvrSkeleton; return true; } #endif return false; } void FOculusHandTracking::InitializeHandMesh(USkeletalMesh* SkeletalMesh, const ovrpMesh* OvrMesh, const float WorldToMeters) { #if WITH_EDITOR FSkeletalMeshLODModel* LodRenderData = &SkeletalMesh->GetImportedModel()->LODModels[0]; // Initialize mesh section LodRenderData->Sections.SetNumUninitialized(1); new (&LodRenderData->Sections[0]) FSkelMeshSection(); auto& MeshSection = LodRenderData->Sections[0]; // Set default mesh section properties MeshSection.MaterialIndex = 0; MeshSection.BaseIndex = 0; MeshSection.NumTriangles = OvrMesh->NumIndices / 3; MeshSection.BaseVertexIndex = 0; MeshSection.MaxBoneInfluences = 4; MeshSection.NumVertices = OvrMesh->NumVertices; float MaxDistSq = MIN_flt; for (uint32_t VertexIndex = 0; VertexIndex < OvrMesh->NumVertices; VertexIndex++) { FSoftSkinVertex SoftVertex; FMemory::Memzero(SoftVertex.InfluenceWeights); FMemory::Memzero(SoftVertex.InfluenceBones); // Update vertex data SoftVertex.Color = FColor::White; ovrpVector3f VertexPosition = OvrMesh->VertexPositions[VertexIndex]; ovrpVector3f Normal = OvrMesh->VertexNormals[VertexIndex]; SoftVertex.Position = FVector3f(VertexPosition.x, VertexPosition.z, VertexPosition.y) * WorldToMeters; SoftVertex.TangentZ = FVector3f(Normal.x, Normal.z, Normal.y); SoftVertex.TangentX = FVector3f(1.0f, 0.0f, 0.0f); SoftVertex.TangentY = FVector3f(0.0f, 1.0f, 0.0f); // SoftVertex.TangentZ^ SoftVertex.TangentX* SoftVertex.TangentZ.W; SoftVertex.UVs[0] = FVector2f(OvrMesh->VertexUV0[VertexIndex].x, OvrMesh->VertexUV0[VertexIndex].y); // Update the Bounds float VertexDistSq = SoftVertex.Position.SizeSquared(); if (VertexDistSq > MaxDistSq) MaxDistSq = VertexDistSq; // Update blend weights and indices ovrpVector4f BlendWeights = OvrMesh->BlendWeights[VertexIndex]; ovrpVector4s BlendIndices = OvrMesh->BlendIndices[VertexIndex]; SoftVertex.InfluenceWeights[0] = UE::AnimationCore::MaxRawBoneWeightFloat * BlendWeights.x; SoftVertex.InfluenceBones[0] = BlendIndices.x; SoftVertex.InfluenceWeights[1] = UE::AnimationCore::MaxRawBoneWeightFloat * BlendWeights.y; SoftVertex.InfluenceBones[1] = BlendIndices.y; SoftVertex.InfluenceWeights[2] = UE::AnimationCore::MaxRawBoneWeightFloat * BlendWeights.z; SoftVertex.InfluenceBones[2] = BlendIndices.z; SoftVertex.InfluenceWeights[3] = UE::AnimationCore::MaxRawBoneWeightFloat * BlendWeights.w; SoftVertex.InfluenceBones[3] = BlendIndices.w; MeshSection.SoftVertices.Add(SoftVertex); } // Update bone map for (uint32 BoneIndex = 0; BoneIndex < (uint32)SkeletalMesh->GetRefSkeleton().GetNum(); BoneIndex++) { MeshSection.BoneMap.Add(BoneIndex); } // Update LOD render data LodRenderData->NumVertices = OvrMesh->NumVertices; LodRenderData->NumTexCoords = 1; // Create index buffer for (uint32_t Index = 0; Index < OvrMesh->NumIndices; Index++) { LodRenderData->IndexBuffer.Add(OvrMesh->Indices[Index]); } // Finalize Bounds float MaxDist = FMath::Sqrt(MaxDistSq); FBoxSphereBounds Bounds; Bounds.Origin = FVector::ZeroVector; Bounds.BoxExtent = FVector(MaxDist); Bounds.SphereRadius = MaxDist; SkeletalMesh->SetImportedBounds(Bounds); #else FSkeletalMeshLODRenderData* LodRenderData = &SkeletalMesh->GetResourceForRendering()->LODRenderData[0]; // Initialize Mesh Section LodRenderData->RenderSections.SetNumUninitialized(1); new (&LodRenderData->RenderSections[0]) FSkelMeshRenderSection(); auto& MeshSection = LodRenderData->RenderSections[0]; // Initialize render section properties MeshSection.MaterialIndex = 0; MeshSection.BaseIndex = 0; MeshSection.NumTriangles = OvrMesh->NumIndices / 3; MeshSection.BaseVertexIndex = 0; MeshSection.MaxBoneInfluences = 4; MeshSection.NumVertices = OvrMesh->NumVertices; MeshSection.bCastShadow = true; MeshSection.bDisabled = false; MeshSection.bRecomputeTangent = false; // Initialize Vertex Buffers LodRenderData->StaticVertexBuffers.PositionVertexBuffer.Init(OvrMesh->NumVertices); LodRenderData->StaticVertexBuffers.StaticMeshVertexBuffer.Init(OvrMesh->NumVertices, 1); LodRenderData->StaticVertexBuffers.ColorVertexBuffer.Init(OvrMesh->NumVertices); // Initialize Skin Weights TArray InWeights; InWeights.AddUninitialized(OvrMesh->NumVertices); float MaxDistSq = MIN_flt; TMap> OverlappingVertices; for (uint32_t VertexIndex = 0; VertexIndex < OvrMesh->NumVertices; VertexIndex++) { FMemory::Memzero(InWeights[VertexIndex].InfluenceWeights); FMemory::Memzero(InWeights[VertexIndex].InfluenceBones); // Initialize vertex data FModelVertex ModelVertex; // Update Model Vertex ovrpVector3f VertexPosition = OvrMesh->VertexPositions[VertexIndex]; ovrpVector3f Normal = OvrMesh->VertexNormals[VertexIndex]; ModelVertex.Position = FVector3f(VertexPosition.x, VertexPosition.z, VertexPosition.y) * WorldToMeters; ModelVertex.TangentZ = FVector3f(Normal.x, Normal.z, Normal.y); ModelVertex.TangentX = FVector3f(1.0f, 0.0f, 0.0f); ModelVertex.TexCoord = FVector2f(OvrMesh->VertexUV0[VertexIndex].x, OvrMesh->VertexUV0[VertexIndex].y); // Add Model Vertex data to vertex buffer LodRenderData->StaticVertexBuffers.PositionVertexBuffer.VertexPosition(VertexIndex) = ModelVertex.Position; LodRenderData->StaticVertexBuffers.StaticMeshVertexBuffer.SetVertexTangents(VertexIndex, ModelVertex.TangentX, ModelVertex.GetTangentY(), ModelVertex.TangentZ); LodRenderData->StaticVertexBuffers.StaticMeshVertexBuffer.SetVertexUV(VertexIndex, 0, ModelVertex.TexCoord); // Update the Bounds float VertexDistSq = ModelVertex.Position.SizeSquared(); if (VertexDistSq > MaxDistSq) MaxDistSq = VertexDistSq; // Set vertex blend weights and indices TArray Vertices; ovrpVector4f BlendWeights = OvrMesh->BlendWeights[VertexIndex]; ovrpVector4s BlendIndices = OvrMesh->BlendIndices[VertexIndex]; InWeights[VertexIndex].InfluenceWeights[0] = UE::AnimationCore::MaxRawBoneWeightFloat * BlendWeights.x; InWeights[VertexIndex].InfluenceBones[0] = BlendIndices.x; Vertices.Add(BlendIndices.x); InWeights[VertexIndex].InfluenceWeights[1] = UE::AnimationCore::MaxRawBoneWeightFloat * BlendWeights.y; InWeights[VertexIndex].InfluenceBones[1] = BlendIndices.y; Vertices.Add(BlendIndices.y); InWeights[VertexIndex].InfluenceWeights[2] = UE::AnimationCore::MaxRawBoneWeightFloat * BlendWeights.z; InWeights[VertexIndex].InfluenceBones[2] = BlendIndices.z; Vertices.Add(BlendIndices.z); InWeights[VertexIndex].InfluenceWeights[3] = UE::AnimationCore::MaxRawBoneWeightFloat * BlendWeights.w; InWeights[VertexIndex].InfluenceBones[3] = BlendIndices.w; Vertices.Add(BlendIndices.w); OverlappingVertices.Add(VertexIndex, Vertices); } // Update bone map for mesh section for (uint32 BoneIndex = 0; BoneIndex < (uint32)SkeletalMesh->GetRefSkeleton().GetNum(); BoneIndex++) { MeshSection.BoneMap.Add(BoneIndex); } // Finalize Bounds float MaxDist = FMath::Sqrt(MaxDistSq); FBoxSphereBounds Bounds; Bounds.Origin = FVector::ZeroVector; Bounds.BoxExtent = FVector(MaxDist); Bounds.SphereRadius = MaxDist; SkeletalMesh->SetImportedBounds(Bounds); // Assign skin weights to vertex buffer LodRenderData->SkinWeightVertexBuffer = InWeights; MeshSection.DuplicatedVerticesBuffer.Init(OvrMesh->NumVertices, OverlappingVertices); // Set index buffer LodRenderData->MultiSizeIndexContainer.CreateIndexBuffer(sizeof(uint16_t)); for (uint32_t Index = 0; Index < OvrMesh->NumIndices; Index++) { LodRenderData->MultiSizeIndexContainer.GetIndexBuffer()->AddItem(OvrMesh->Indices[Index]); } #endif } void FOculusHandTracking::InitializeHandSkeleton(USkeletalMesh* SkeletalMesh, const ovrpSkeleton2* OvrSkeleton, const float WorldToMeters) { SkeletalMesh->GetRefSkeleton().Empty(OvrSkeleton->NumBones); #if WITH_EDITOR FSkeletalMeshLODModel* LodRenderData = &SkeletalMesh->GetImportedModel()->LODModels[0]; #else FSkeletalMeshLODRenderData* LodRenderData = &SkeletalMesh->GetResourceForRendering()->LODRenderData[0]; #endif SkeletalMesh->SetHasBeenSimplified(false); SkeletalMesh->SetHasVertexColors(true); checkf(OvrSkeleton->NumBones <= static_cast(TNumericLimits::Max()), TEXT("Bone indices are stored as uint8 type.")); for (uint8 BoneIndex = 0; BoneIndex < static_cast(OvrSkeleton->NumBones); BoneIndex++) { LodRenderData->ActiveBoneIndices.Add(BoneIndex); LodRenderData->RequiredBones.Add(BoneIndex); FText BoneDisplayName; if (!FindBoneDisplayName(BoneDisplayName, BoneIndex)) { UE_LOG(LogOcHandTracking, Error, TEXT("Cannot find bone display name for bone index: %d."), BoneIndex) continue; } FString BoneString = BoneDisplayName.ToString(); FName BoneName = FName(*BoneString); FTransform Transform = FTransform::Identity; FVector BonePosition = OvrBoneVectorToFVector(OvrSkeleton->Bones[BoneIndex].Pose.Position, WorldToMeters); FQuat BoneRotation = BoneIndex == 0 ? FQuat(-1.0f, 0.0f, 0.0f, 1.0f) : OvrBoneQuatToFQuat(OvrSkeleton->Bones[BoneIndex].Pose.Orientation); Transform.SetLocation(BonePosition); Transform.SetRotation(BoneRotation); FReferenceSkeletonModifier Modifier = FReferenceSkeletonModifier(SkeletalMesh->GetRefSkeleton(), nullptr); int32 ParentIndex = -1; if (BoneIndex > 0) { if (OvrSkeleton->Bones[BoneIndex].ParentBoneIndex == ovrpBoneId::ovrpBoneId_Invalid) { ParentIndex = 0; } else { ParentIndex = OvrSkeleton->Bones[BoneIndex].ParentBoneIndex; } } Modifier.Add(FMeshBoneInfo(BoneName, BoneString, ParentIndex), Transform); } SkeletalMesh->CalculateInvRefMatrices(); } TArray FOculusHandTracking::InitializeHandPhysics(const EOculusXRHandType SkeletonType, USkinnedMeshComponent* HandComponent, const float WorldToMeters) { TArray CollisionCapsules; ovrpSkeleton2* OvrSkeleton = new ovrpSkeleton2(); #if OCULUS_INPUT_SUPPORTED_PLATFORMS ovrpSkeletonType OvrSkeletonType = (ovrpSkeletonType)((int32)SkeletonType - 1); if (FOculusXRHMDModule::GetPluginWrapper().GetSkeleton2(OvrSkeletonType, OvrSkeleton) != ovrpSuccess) { #if !WITH_EDITOR UE_LOG(LogOcHandTracking, Error, TEXT("Failed to get skeleton data from Oculus runtime.")); #endif delete OvrSkeleton; return CollisionCapsules; } #endif TArray IgnoreCapsules; CollisionCapsules.AddDefaulted(OvrSkeleton->NumBoneCapsules); for (uint32 CapsuleIndex = 0; CapsuleIndex < OvrSkeleton->NumBoneCapsules; CapsuleIndex++) { ovrpBoneCapsule OvrBoneCapsule = OvrSkeleton->BoneCapsules[CapsuleIndex]; UCapsuleComponent* Capsule = NewObject(HandComponent); FVector CapsulePointZero = OvrBoneVectorToFVector(OvrBoneCapsule.Points[0], WorldToMeters); FVector CapsulePointOne = OvrBoneVectorToFVector(OvrBoneCapsule.Points[1], WorldToMeters); FVector Delta = (CapsulePointOne - CapsulePointZero); FName BoneName = HandComponent->GetSkinnedAsset()->GetRefSkeleton().GetBoneName(OvrBoneCapsule.BoneIndex); float CapsuleHeight = Delta.Size(); float CapsuleRadius = OvrBoneCapsule.Radius * WorldToMeters; Capsule->SetCapsuleRadius(CapsuleRadius); Capsule->SetCapsuleHalfHeight(Delta.Size() / 2 + CapsuleRadius); Capsule->SetupAttachment(HandComponent, BoneName); Capsule->SetCollisionProfileName(HandComponent->GetCollisionProfileName()); Capsule->RegisterComponentWithWorld(HandComponent->GetWorld()); Capsule->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); FRotator CapsuleRotation = FQuat::FindBetweenVectors(FVector::RightVector, Delta).Rotator() + FRotator(0, 0, 90); ; Capsule->SetRelativeRotation(CapsuleRotation); Capsule->SetRelativeLocation(CapsulePointZero + (Delta / 2)); CollisionCapsules[CapsuleIndex].Capsule = Capsule; CollisionCapsules[CapsuleIndex].BoneId = (EOculusXRBone)OvrBoneCapsule.BoneIndex; IgnoreCapsules.Add(Capsule); } for (int32 CapsuleIndex = 0; CapsuleIndex < CollisionCapsules.Num(); CapsuleIndex++) { CollisionCapsules[CapsuleIndex].Capsule->MoveIgnoreComponents = IgnoreCapsules; } return CollisionCapsules; } ovrpBoneId FOculusHandTracking::ToOvrBone(EOculusXRBone Bone) { if (Bone > EOculusXRBone::Bone_Max) return ovrpBoneId_Invalid; return static_cast(Bone); } FString FOculusHandTracking::GetBoneName(const uint8 Bone) { FText DisplayName; if (FindBoneDisplayName(DisplayName, Bone)) { return DisplayName.ToString(); } if (FindBoneDisplayName(DisplayName, static_cast(EOculusXRBone::Invalid))) { return DisplayName.ToString(); } return { "Invalid" }; } bool FOculusHandTracking::FindBoneDisplayName(FText& DisplayName, uint8 Bone) { return StaticEnum()->FindDisplayNameTextByValue(DisplayName, Bone); } EOculusXRTrackingConfidence FOculusHandTracking::ToEOculusXRTrackingConfidence(ovrpTrackingConfidence Confidence) { EOculusXRTrackingConfidence TrackingConfidence = EOculusXRTrackingConfidence::Low; switch (Confidence) { case ovrpTrackingConfidence_Low: TrackingConfidence = EOculusXRTrackingConfidence::Low; break; case ovrpTrackingConfidence_High: TrackingConfidence = EOculusXRTrackingConfidence::High; break; } return TrackingConfidence; } FVector FOculusHandTracking::OvrBoneVectorToFVector(ovrpVector3f ovrpVector, float WorldToMeters) { return FVector(ovrpVector.x, -ovrpVector.y, ovrpVector.z) * WorldToMeters; } FQuat FOculusHandTracking::OvrBoneQuatToFQuat(ovrpQuatf ovrpQuat) { return FQuat(ovrpQuat.x, -ovrpQuat.y, ovrpQuat.z, -ovrpQuat.w); } EOculusXRControllerDrivenHandPoseTypes FOculusHandTracking::ControllerDrivenHandType = EOculusXRControllerDrivenHandPoseTypes::None; void FOculusHandTracking::SetControllerDrivenHandPoses(const EOculusXRControllerDrivenHandPoseTypes Type) { FOculusHandTracking::ControllerDrivenHandType = Type; switch (Type) { case EOculusXRControllerDrivenHandPoseTypes::None: FOculusXRHMDModule::GetPluginWrapper().SetControllerDrivenHandPoses(false); FOculusXRHMDModule::GetPluginWrapper().SetControllerDrivenHandPosesAreNatural(false); break; case EOculusXRControllerDrivenHandPoseTypes::Natural: FOculusXRHMDModule::GetPluginWrapper().SetControllerDrivenHandPoses(true); FOculusXRHMDModule::GetPluginWrapper().SetControllerDrivenHandPosesAreNatural(true); break; case EOculusXRControllerDrivenHandPoseTypes::Controller: FOculusXRHMDModule::GetPluginWrapper().SetControllerDrivenHandPoses(true); FOculusXRHMDModule::GetPluginWrapper().SetControllerDrivenHandPosesAreNatural(false); break; } } } // namespace OculusXRInput