Android build settings + metaxr

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

View File

@@ -0,0 +1,474 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRBodytrackingXR.h"
#include "OpenXRCore.h"
#include "IOpenXRHMDModule.h"
#include "OpenXRHMD.h"
#include "OculusXRMovementLog.h"
#include "OpenXR/OculusXROpenXRUtilities.h"
#define LOCTEXT_NAMESPACE "OculusXRMovement"
namespace XRMovement
{
PFN_xrCreateBodyTrackerFB xrCreateBodyTrackerFB = nullptr;
PFN_xrDestroyBodyTrackerFB xrDestroyBodyTrackerFB = nullptr;
PFN_xrLocateBodyJointsFB xrLocateBodyJointsFB = nullptr;
PFN_xrGetBodySkeletonFB xrGetBodySkeletonFB = nullptr;
PFN_xrRequestBodyTrackingFidelityMETA xrRequestBodyTrackingFidelityMETA = nullptr;
PFN_xrSuggestBodyTrackingCalibrationOverrideMETA xrSuggestBodyTrackingCalibrationOverrideMETA = nullptr;
PFN_xrResetBodyTrackingCalibrationMETA xrResetBodyTrackingCalibrationMETA = nullptr;
FBodyTrackingXR::FBodyTrackingXR()
: bExtBodyTrackingEnabled(false)
, bExtBodyTrackingFullBodyEnabled(false)
, bExtBodyTrackingFidelityEnabled(false)
, bExtBodyTrackingCalibrationEnabled(false)
, OpenXRHMD(nullptr)
, BodyTracker(nullptr)
, FullBodyTracking(false)
{
}
FBodyTrackingXR::~FBodyTrackingXR()
{
}
void FBodyTrackingXR::RegisterAsOpenXRExtension()
{
#if defined(WITH_OCULUS_BRANCH)
// Feature not enabled on Marketplace build. Currently only for the meta fork
RegisterOpenXRExtensionModularFeature();
#endif
}
bool FBodyTrackingXR::GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions)
{
OutExtensions.Add(XR_FB_BODY_TRACKING_EXTENSION_NAME);
return true;
}
bool FBodyTrackingXR::GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions)
{
OutExtensions.Add(XR_META_BODY_TRACKING_FULL_BODY_EXTENSION_NAME);
OutExtensions.Add(XR_META_BODY_TRACKING_FIDELITY_EXTENSION_NAME);
OutExtensions.Add(XR_META_BODY_TRACKING_CALIBRATION_EXTENSION_NAME);
return true;
}
const void* FBodyTrackingXR::OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext)
{
if (InModule != nullptr)
{
bExtBodyTrackingEnabled = InModule->IsExtensionEnabled(XR_FB_BODY_TRACKING_EXTENSION_NAME);
bExtBodyTrackingFullBodyEnabled = InModule->IsExtensionEnabled(XR_META_BODY_TRACKING_FULL_BODY_EXTENSION_NAME);
bExtBodyTrackingFidelityEnabled = InModule->IsExtensionEnabled(XR_META_BODY_TRACKING_FIDELITY_EXTENSION_NAME);
bExtBodyTrackingCalibrationEnabled = InModule->IsExtensionEnabled(XR_META_BODY_TRACKING_CALIBRATION_EXTENSION_NAME);
UE_LOG(LogOculusXRMovement, Log, TEXT("[Body Tracking] Extensions available: Tracking: %hs -- Full Body: %hs -- Fidelity: %hs -- Calibration: %hs"),
bExtBodyTrackingEnabled ? "ENABLED" : "DISABLED",
bExtBodyTrackingFullBodyEnabled ? "ENABLED" : "DISABLED",
bExtBodyTrackingFidelityEnabled ? "ENABLED" : "DISABLED",
bExtBodyTrackingCalibrationEnabled ? "ENABLED" : "DISABLED");
}
return InNext;
}
const void* FBodyTrackingXR::OnCreateSession(XrInstance InInstance, XrSystemId InSystem, const void* InNext)
{
InitOpenXRFunctions(InInstance);
OpenXRHMD = (FOpenXRHMD*)GEngine->XRSystem.Get();
return InNext;
}
void FBodyTrackingXR::OnDestroySession(XrSession InSession)
{
OpenXRHMD = nullptr;
}
void* FBodyTrackingXR::OnWaitFrame(XrSession InSession, void* InNext)
{
Update_GameThread(InSession);
return InNext;
}
XrResult FBodyTrackingXR::StartBodyTracking()
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StartBodyTracking] XR state is invalid."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsBodyTrackingSupported())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StartBodyTracking] Body tracking is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (BodyTracker != XR_NULL_HANDLE)
{
UE_LOG(LogOculusXRMovement, Log, TEXT("[StartBodyTracking] Body tracking is already started."));
return XR_SUCCESS;
}
XrBodyTrackerCreateInfoFB createInfo = { XR_TYPE_BODY_TRACKER_CREATE_INFO_FB };
createInfo.next = nullptr;
createInfo.bodyJointSet = XR_BODY_JOINT_SET_DEFAULT_FB;
auto result = XRMovement::xrCreateBodyTrackerFB(OpenXRHMD->GetSession(), &createInfo, &BodyTracker);
if (XR_FAILED(result))
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StartBodyTracking] Body tracking failed to start. Result: %d"), result);
return result;
}
return XR_SUCCESS;
}
XrResult FBodyTrackingXR::StartBodyTrackingByJointSet(EOculusXRBodyJointSet jointSet)
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StartBodyTrackingByJointSet] XR state is invalid."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsBodyTrackingSupported())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StartBodyTrackingByJointSet] Body tracking is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (BodyTracker != XR_NULL_HANDLE)
{
UE_LOG(LogOculusXRMovement, Log, TEXT("[StartBodyTrackingByJointSet] Body tracking is already started."));
return XR_SUCCESS;
}
XrBodyTrackerCreateInfoFB createInfo = { XR_TYPE_BODY_TRACKER_CREATE_INFO_FB };
createInfo.next = nullptr;
switch (jointSet)
{
case EOculusXRBodyJointSet::UpperBody:
createInfo.bodyJointSet = XR_BODY_JOINT_SET_DEFAULT_FB;
break;
case EOculusXRBodyJointSet::FullBody:
createInfo.bodyJointSet = XR_BODY_JOINT_SET_FULL_BODY_META;
if (!IsFullBodySupported())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StartBodyTrackingByJointSet] Full body tracking is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
break;
default:
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StartBodyTrackingByJointSet] Unknown body tracking joint set."));
return XR_ERROR_VALIDATION_FAILURE;
}
auto result = XRMovement::xrCreateBodyTrackerFB(OpenXRHMD->GetSession(), &createInfo, &BodyTracker);
if XR_FAILED (result)
{
BodyTracker = XR_NULL_HANDLE;
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StartBodyTrackingByJointSet] Body tracking failed to start. Result: %d"), result);
return result;
}
else
{
FullBodyTracking = (jointSet == EOculusXRBodyJointSet::FullBody);
}
return XR_SUCCESS;
}
XrResult FBodyTrackingXR::StopBodyTracking()
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StopBodyTracking] XR state is invalid."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsBodyTrackingSupported())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StopBodyTracking] Body tracking is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
XrResult result = XR_SUCCESS;
if (IsBodyTrackingEnabled())
{
result = XRMovement::xrDestroyBodyTrackerFB(BodyTracker);
if XR_FAILED (result)
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StopBodyTracking] Body tracking failed to stop. Result: %d"), result);
}
}
BodyTracker = XR_NULL_HANDLE;
FullBodyTracking = false;
return result;
}
XrResult FBodyTrackingXR::GetCachedBodyState(FOculusXRBodyState& OutState)
{
if (!IsBodyTrackingEnabled())
{
return XR_ERROR_VALIDATION_FAILURE;
}
OutState = CachedBodyState;
return XR_SUCCESS;
}
XrResult FBodyTrackingXR::GetBodySkeleton(FOculusXRBodySkeleton& OutSkeleton)
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[GetBodySkeleton] XR state is invalid."));
return XR_ERROR_VALIDATION_FAILURE;
}
int jointCount = IsFullBodyTrackingEnabled() ? (int)XR_FULL_BODY_JOINT_COUNT_META : (int)XR_BODY_JOINT_COUNT_FB;
// Allocate enough memory for the larger joint set
static_assert((int)XR_FULL_BODY_JOINT_COUNT_META >= (int)XR_BODY_JOINT_COUNT_FB);
XrBodySkeletonJointFB joints[XR_FULL_BODY_JOINT_COUNT_META];
XrBodySkeletonFB bodySkeleton = { XR_TYPE_BODY_SKELETON_FB };
bodySkeleton.jointCount = jointCount;
bodySkeleton.joints = joints;
auto result = XRMovement::xrGetBodySkeletonFB(BodyTracker, &bodySkeleton);
if (XR_FAILED(result))
{
return result;
}
OutSkeleton.NumBones = bodySkeleton.jointCount;
for (uint32 i = 0; i < bodySkeleton.jointCount; ++i)
{
XrBodySkeletonJointFB bone = bodySkeleton.joints[i];
XrPosef bonePose = bone.pose;
FOculusXRBodySkeletonBone& OculusXRBone = OutSkeleton.Bones[i];
OculusXRBone.Orientation = FRotator(ToFQuat(bonePose.orientation));
OculusXRBone.Position = ToFVector(bonePose.position) * OpenXRHMD->GetWorldToMetersScale();
if (bone.parentJoint == XR_BODY_JOINT_NONE_FB)
{
OculusXRBone.ParentBoneIndex = EOculusXRBoneID::None;
}
else
{
OculusXRBone.ParentBoneIndex = static_cast<EOculusXRBoneID>(bone.parentJoint);
}
OculusXRBone.BoneId = static_cast<EOculusXRBoneID>(bone.joint);
}
return XR_SUCCESS;
}
XrResult FBodyTrackingXR::RequestBodyTrackingFidelity(EOculusXRBodyTrackingFidelity Fidelity)
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[RequestBodyTrackingFidelity] XR state is invalid."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsFidelitySupported())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[RequestBodyTrackingFidelity] Fidelity is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (BodyTracker == XR_NULL_HANDLE)
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[RequestBodyTrackingFidelity] Body tracking is not started."));
return XR_SUCCESS;
}
XrBodyTrackingFidelityMETA fidelity;
switch (Fidelity)
{
case EOculusXRBodyTrackingFidelity::High:
fidelity = XR_BODY_TRACKING_FIDELITY_HIGH_META;
break;
case EOculusXRBodyTrackingFidelity::Low:
fidelity = XR_BODY_TRACKING_FIDELITY_LOW_META;
break;
default:
UE_LOG(LogOculusXRMovement, Warning, TEXT("[RequestBodyTrackingFidelity] Invalid fidelity level."));
return XR_ERROR_VALIDATION_FAILURE;
}
XrResult result = xrRequestBodyTrackingFidelityMETA(BodyTracker, fidelity);
if (XR_FAILED(result))
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[RequestBodyTrackingFidelity] Failed to request fidelity level. Result: %d"), result);
}
return result;
}
XrResult FBodyTrackingXR::ResetBodyTrackingFidelity()
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[ResetBodyTrackingFidelity] XR state is invalid."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsFidelitySupported())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[ResetBodyTrackingFidelity] Fidelity is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (BodyTracker == XR_NULL_HANDLE)
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[ResetBodyTrackingFidelity] Body tracking is not started."));
return XR_SUCCESS;
}
XrResult result = xrResetBodyTrackingCalibrationMETA(BodyTracker);
if (XR_FAILED(result))
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[ResetBodyTrackingFidelity] Failed to request fidelity level. Result: %d"), result);
}
return result;
}
XrResult FBodyTrackingXR::SuggestBodyTrackingCalibrationOverride(float height)
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[SuggestBodyTrackingCalibrationOverride] XR state is invalid."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsCalibrationSupported())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[SuggestBodyTrackingCalibrationOverride] Calibration is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (BodyTracker == XR_NULL_HANDLE)
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[SuggestBodyTrackingCalibrationOverride] Body tracking is not started."));
return XR_SUCCESS;
}
XrBodyTrackingCalibrationInfoMETA xrCalibrationInfo = { XR_TYPE_BODY_TRACKING_CALIBRATION_INFO_META };
xrCalibrationInfo.bodyHeight = height;
XrResult result = xrSuggestBodyTrackingCalibrationOverrideMETA(BodyTracker, &xrCalibrationInfo);
if (XR_FAILED(result))
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[SuggestBodyTrackingCalibrationOverride] failed to suggest calibration override! Result: %d"), result);
}
return result;
}
void FBodyTrackingXR::InitOpenXRFunctions(XrInstance InInstance)
{
// XR_FB_Body_Tracking
OculusXR::XRGetInstanceProcAddr(InInstance, "xrCreateBodyTrackerFB", &xrCreateBodyTrackerFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrDestroyBodyTrackerFB", &xrDestroyBodyTrackerFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrLocateBodyJointsFB", &xrLocateBodyJointsFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrGetBodySkeletonFB", &xrGetBodySkeletonFB);
// XR_META_body_tracking_fidelity
OculusXR::XRGetInstanceProcAddr(InInstance, "xrRequestBodyTrackingFidelityMETA", &xrRequestBodyTrackingFidelityMETA);
// XR_META_body_tracking_calibration
OculusXR::XRGetInstanceProcAddr(InInstance, "xrSuggestBodyTrackingCalibrationOverrideMETA", &xrSuggestBodyTrackingCalibrationOverrideMETA);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrResetBodyTrackingCalibrationMETA", &xrResetBodyTrackingCalibrationMETA);
}
void FBodyTrackingXR::Update_GameThread(XrSession InSession)
{
check(IsInGameThread());
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession() || !IsBodyTrackingSupported() || !IsBodyTrackingEnabled())
{
return;
}
static_assert(XR_FULL_BODY_JOINT_COUNT_META == static_cast<int>(EOculusXRBoneID::COUNT), "The size of the XR Bone ID enum should be the same as the EOculusXRBoneID count.");
int jointCount = IsFullBodyTrackingEnabled() ? (int)XR_FULL_BODY_JOINT_COUNT_META : (int)XR_BODY_JOINT_COUNT_FB;
CachedBodyState.Joints.SetNum(static_cast<int>(XR_FULL_BODY_JOINT_COUNT_META));
XrBodyJointsLocateInfoFB info = { XR_TYPE_BODY_JOINTS_LOCATE_INFO_FB };
info.baseSpace = OpenXRHMD->GetTrackingSpace();
info.time = OpenXRHMD->GetDisplayTime();
XrBodyJointLocationsFB locations = { XR_TYPE_BODY_JOINT_LOCATIONS_FB };
XrBodyJointLocationFB jointLocations[XR_FULL_BODY_JOINT_COUNT_META];
locations.jointCount = jointCount;
locations.jointLocations = jointLocations;
XrBodyTrackingCalibrationStatusMETA calibrationStatus = { XR_TYPE_BODY_TRACKING_CALIBRATION_STATUS_META };
calibrationStatus.next = XR_NULL_HANDLE;
if (IsCalibrationSupported())
{
OculusXR::XRAppendToChain(
reinterpret_cast<XrBaseOutStructure*>(&calibrationStatus), reinterpret_cast<XrBaseOutStructure*>(&locations));
}
XrBodyTrackingFidelityStatusMETA fidelityStatus = { XR_TYPE_BODY_TRACKING_FIDELITY_STATUS_META };
fidelityStatus.next = XR_NULL_HANDLE;
if (IsFidelitySupported())
{
OculusXR::XRAppendToChain(
reinterpret_cast<XrBaseOutStructure*>(&fidelityStatus), reinterpret_cast<XrBaseOutStructure*>(&locations));
}
auto result = XRMovement::xrLocateBodyJointsFB(BodyTracker, &info, &locations);
if (XR_FAILED(result))
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[LocateBodyJoints] Failed to locate joints! Result: %d"), result);
return;
}
CachedBodyState.IsActive = (bool)locations.isActive;
CachedBodyState.Confidence = locations.confidence;
CachedBodyState.SkeletonChangedCount = locations.skeletonChangedCount;
CachedBodyState.Time = locations.time * 1e-9; // FromXrTime
for (int i = 0; i < jointCount; ++i)
{
XrBodyJointLocationFB jointLocation = locations.jointLocations[i];
XrPosef jointPose = jointLocation.pose;
FOculusXRBodyJoint& OculusXRBodyJoint = CachedBodyState.Joints[i];
OculusXRBodyJoint.LocationFlags = jointLocation.locationFlags;
OculusXRBodyJoint.bIsValid = jointLocation.locationFlags & (XRSpaceFlags::XR_SPACE_LOCATION_ORIENTATION_VALID_BIT | XRSpaceFlags::XR_SPACE_LOCATION_POSITION_VALID_BIT);
OculusXRBodyJoint.Orientation = FRotator(ToFQuat(jointPose.orientation));
OculusXRBodyJoint.Position = ToFVector(jointPose.position) * OpenXRHMD->GetWorldToMetersScale();
}
// If using less joints than the max count we can just set the remaining joints to null
if (jointCount < CachedBodyState.Joints.Num())
{
for (int i = jointCount; i < CachedBodyState.Joints.Num(); ++i)
{
CachedBodyState.Joints[i].bIsValid = false;
}
}
}
} // namespace XRMovement
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,74 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "OculusXRMovementXRIncludes.h"
#include "IOpenXRExtensionPlugin.h"
#include "OculusXRMovementTypes.h"
#define LOCTEXT_NAMESPACE "OculusXRMovement"
class FOpenXRHMD;
namespace XRMovement
{
extern PFN_xrCreateBodyTrackerFB xrCreateBodyTrackerFB;
extern PFN_xrDestroyBodyTrackerFB xrDestroyBodyTrackerFB;
extern PFN_xrLocateBodyJointsFB xrLocateBodyJointsFB;
extern PFN_xrGetBodySkeletonFB xrGetBodySkeletonFB;
extern PFN_xrRequestBodyTrackingFidelityMETA xrRequestBodyTrackingFidelityMETA;
extern PFN_xrSuggestBodyTrackingCalibrationOverrideMETA xrSuggestBodyTrackingCalibrationOverrideMETA;
extern PFN_xrResetBodyTrackingCalibrationMETA xrResetBodyTrackingCalibrationMETA;
class FBodyTrackingXR : public IOpenXRExtensionPlugin
{
public:
// IOculusXROpenXRHMDPlugin
virtual bool GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
virtual bool GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
virtual const void* OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext) override;
virtual const void* OnCreateSession(XrInstance InInstance, XrSystemId InSystem, const void* InNext) override;
virtual void OnDestroySession(XrSession InSession) override;
virtual void* OnWaitFrame(XrSession InSession, void* InNext) override;
public:
FBodyTrackingXR();
virtual ~FBodyTrackingXR();
void RegisterAsOpenXRExtension();
bool IsBodyTrackingSupported() const { return bExtBodyTrackingEnabled; }
bool IsFullBodySupported() const { return bExtBodyTrackingFullBodyEnabled; }
bool IsFidelitySupported() const { return bExtBodyTrackingFidelityEnabled; }
bool IsCalibrationSupported() const { return bExtBodyTrackingCalibrationEnabled; }
bool IsBodyTrackingEnabled() const { return BodyTracker != XR_NULL_HANDLE; }
bool IsFullBodyTrackingEnabled() const { return FullBodyTracking; }
XrResult StartBodyTracking();
XrResult StartBodyTrackingByJointSet(EOculusXRBodyJointSet jointSet);
XrResult StopBodyTracking();
XrResult GetCachedBodyState(FOculusXRBodyState& OutState);
XrResult GetBodySkeleton(FOculusXRBodySkeleton& OutSkeleton);
XrResult RequestBodyTrackingFidelity(EOculusXRBodyTrackingFidelity Fidelity);
XrResult ResetBodyTrackingFidelity();
XrResult SuggestBodyTrackingCalibrationOverride(float height);
private:
void InitOpenXRFunctions(XrInstance InInstance);
void Update_GameThread(XrSession InSession);
bool bExtBodyTrackingEnabled;
bool bExtBodyTrackingFullBodyEnabled;
bool bExtBodyTrackingFidelityEnabled;
bool bExtBodyTrackingCalibrationEnabled;
FOpenXRHMD* OpenXRHMD;
FOculusXRBodyState CachedBodyState;
XrBodyTrackerFB BodyTracker = XR_NULL_HANDLE;
bool FullBodyTracking{ false };
};
} // namespace XRMovement
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,192 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXREyeTrackingXR.h"
#include "OpenXRCore.h"
#include "IOpenXRHMDModule.h"
#include "OpenXRHMD.h"
#include "OculusXRMovementLog.h"
#include "OpenXR/OculusXROpenXRUtilities.h"
#define LOCTEXT_NAMESPACE "OculusXRMovement"
namespace XRMovement
{
PFN_xrCreateEyeTrackerFB xrCreateEyeTrackerFB = nullptr;
PFN_xrDestroyEyeTrackerFB xrDestroyEyeTrackerFB = nullptr;
PFN_xrGetEyeGazesFB xrGetEyeGazesFB = nullptr;
FEyeTrackingXR::FEyeTrackingXR()
: bExtEyeTrackingEnabled(false)
, OpenXRHMD(nullptr)
, EyeTracker(nullptr)
{
CachedEyeState.EyeGazes.SetNum(2);
}
FEyeTrackingXR::~FEyeTrackingXR()
{
}
void FEyeTrackingXR::RegisterAsOpenXRExtension()
{
#if defined(WITH_OCULUS_BRANCH)
// Feature not enabled on Marketplace build. Currently only for the meta fork
RegisterOpenXRExtensionModularFeature();
#endif
}
bool FEyeTrackingXR::GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions)
{
OutExtensions.Add(XR_FB_EYE_TRACKING_SOCIAL_EXTENSION_NAME);
return true;
}
bool FEyeTrackingXR::GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions)
{
return true;
}
const void* FEyeTrackingXR::OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext)
{
if (InModule != nullptr)
{
bExtEyeTrackingEnabled = InModule->IsExtensionEnabled(XR_FB_EYE_TRACKING_SOCIAL_EXTENSION_NAME);
}
return InNext;
}
const void* FEyeTrackingXR::OnCreateSession(XrInstance InInstance, XrSystemId InSystem, const void* InNext)
{
InitOpenXRFunctions(InInstance);
OpenXRHMD = (FOpenXRHMD*)GEngine->XRSystem.Get();
return InNext;
}
void FEyeTrackingXR::OnDestroySession(XrSession InSession)
{
OpenXRHMD = nullptr;
}
void* FEyeTrackingXR::OnWaitFrame(XrSession InSession, void* InNext)
{
Update_GameThread(InSession);
return InNext;
}
XrResult FEyeTrackingXR::StartEyeTracking()
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StartEyeTracking] Cannot start eye tracking, the instance or session is null."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsEyeTrackingSupported())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StartEyeTracking] Cannot start eye tracking, body tracking is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (EyeTracker != XR_NULL_HANDLE)
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StartEyeTracking] Cannot start eye tracking, body tracking is already started."));
return XR_SUCCESS;
}
XrEyeTrackerCreateInfoFB createInfo = { XR_TYPE_EYE_TRACKER_CREATE_INFO_FB, nullptr };
auto result = XRMovement::xrCreateEyeTrackerFB(OpenXRHMD->GetSession(), &createInfo, &EyeTracker);
if (XR_FAILED(result))
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StartEyeTracking] Failed to start eye tracking. Result(%d)"), result);
}
return result;
}
XrResult FEyeTrackingXR::StopEyeTracking()
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StopEyeTracking] Cannot stop eye tracking, the instance or session is null."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsEyeTrackingSupported())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StopEyeTracking] Cannot stop eye tracking, eye tracking is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
XrResult result = XR_SUCCESS;
if (IsEyeTrackingEnabled())
{
result = XRMovement::xrDestroyEyeTrackerFB(EyeTracker);
if (XR_FAILED(result))
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StopEyeTracking] Failed to stop eye tracking. Result(%d)"), result);
}
}
EyeTracker = XR_NULL_HANDLE;
return result;
}
XrResult FEyeTrackingXR::GetCachedEyeState(FOculusXREyeGazesState& OutState)
{
if (!IsEyeTrackingEnabled())
{
return XR_ERROR_VALIDATION_FAILURE;
}
OutState = CachedEyeState;
return XR_SUCCESS;
}
void FEyeTrackingXR::InitOpenXRFunctions(XrInstance InInstance)
{
// XR_FB_Eye_Tracking_Social
OculusXR::XRGetInstanceProcAddr(InInstance, "xrCreateEyeTrackerFB", &xrCreateEyeTrackerFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrDestroyEyeTrackerFB", &xrDestroyEyeTrackerFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrGetEyeGazesFB", &xrGetEyeGazesFB);
}
void FEyeTrackingXR::Update_GameThread(XrSession InSession)
{
check(IsInGameThread());
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession() || !IsEyeTrackingSupported() || !IsEyeTrackingEnabled())
{
return;
}
XrEyeGazesInfoFB info{ XR_TYPE_EYE_GAZES_INFO_FB, nullptr };
info.baseSpace = OpenXRHMD->GetTrackingSpace();
info.time = OpenXRHMD->GetDisplayTime();
XrEyeGazesFB gazes{ XR_TYPE_EYE_GAZES_FB, nullptr };
auto result = XRMovement::xrGetEyeGazesFB(EyeTracker, &info, &gazes);
if (XR_FAILED(result))
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[EyeGazeStateUpdate] Failed to get gazes state. Result(%d)"), result);
return;
}
auto ApplyGazeToUEType = [this](const XrEyeGazeFB& xrGaze, FOculusXREyeGazeState& outUEGaze) {
outUEGaze.bIsValid = static_cast<bool>(xrGaze.isValid);
outUEGaze.Confidence = xrGaze.gazeConfidence;
outUEGaze.Orientation = FRotator(ToFQuat(xrGaze.gazePose.orientation));
outUEGaze.Position = ToFVector(xrGaze.gazePose.position) * OpenXRHMD->GetWorldToMetersScale();
};
ApplyGazeToUEType(gazes.gaze[0], CachedEyeState.EyeGazes[0]);
ApplyGazeToUEType(gazes.gaze[1], CachedEyeState.EyeGazes[1]);
}
} // namespace XRMovement
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,55 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "OculusXRMovementXRIncludes.h"
#include "IOpenXRExtensionPlugin.h"
#include "OculusXRMovementTypes.h"
#define LOCTEXT_NAMESPACE "OculusXRMovement"
class FOpenXRHMD;
namespace XRMovement
{
extern PFN_xrCreateEyeTrackerFB xrCreateEyeTrackerFB;
extern PFN_xrDestroyEyeTrackerFB xrDestroyEyeTrackerFB;
extern PFN_xrGetEyeGazesFB xrGetEyeGazesFB;
class FEyeTrackingXR : public IOpenXRExtensionPlugin
{
public:
// IOculusXROpenXRHMDPlugin
virtual bool GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
virtual bool GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
virtual const void* OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext) override;
virtual const void* OnCreateSession(XrInstance InInstance, XrSystemId InSystem, const void* InNext) override;
virtual void OnDestroySession(XrSession InSession) override;
virtual void* OnWaitFrame(XrSession InSession, void* InNext) override;
public:
FEyeTrackingXR();
virtual ~FEyeTrackingXR();
void RegisterAsOpenXRExtension();
bool IsEyeTrackingSupported() const { return bExtEyeTrackingEnabled; }
bool IsEyeTrackingEnabled() const { return EyeTracker != XR_NULL_HANDLE; }
XrResult StartEyeTracking();
XrResult StopEyeTracking();
XrResult GetCachedEyeState(FOculusXREyeGazesState& OutState);
private:
void InitOpenXRFunctions(XrInstance InInstance);
void Update_GameThread(XrSession InSession);
bool bExtEyeTrackingEnabled;
FOpenXRHMD* OpenXRHMD;
FOculusXREyeGazesState CachedEyeState;
XrEyeTrackerFB EyeTracker = XR_NULL_HANDLE;
};
} // namespace XRMovement
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,259 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRFaceTrackingXR.h"
#include "OpenXRCore.h"
#include "IOpenXRHMDModule.h"
#include "OpenXRHMD.h"
#include "OculusXRMovementLog.h"
#include "OpenXR/OculusXROpenXRUtilities.h"
#define LOCTEXT_NAMESPACE "OculusXRMovement"
namespace XRMovement
{
PFN_xrCreateFaceTracker2FB xrCreateFaceTracker2FB = nullptr;
PFN_xrDestroyFaceTracker2FB xrDestroyFaceTracker2FB = nullptr;
PFN_xrGetFaceExpressionWeights2FB xrGetFaceExpressionWeights2FB = nullptr;
FFaceTrackingXR::FFaceTrackingXR()
: bExtFaceTrackingSupported(false)
, bExtFaceTrackingVisemesSupported(false)
, bVisemesEnabled(false)
, OpenXRHMD(nullptr)
, FaceTracker(nullptr)
{
CachedFaceState.ExpressionWeights.SetNum(XR_FACE_EXPRESSION2_COUNT_FB);
CachedFaceState.ExpressionWeightConfidences.SetNum(XR_FACE_CONFIDENCE2_COUNT_FB);
CachedVisemeState.ExpressionVisemeWeights.SetNum(XR_FACE_TRACKING_VISEME_COUNT_METAX1);
}
FFaceTrackingXR::~FFaceTrackingXR()
{
}
void FFaceTrackingXR::RegisterAsOpenXRExtension()
{
#if defined(WITH_OCULUS_BRANCH)
// Feature not enabled on Marketplace build. Currently only for the meta fork
RegisterOpenXRExtensionModularFeature();
#endif
}
bool FFaceTrackingXR::GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions)
{
OutExtensions.Add(XR_FB_FACE_TRACKING2_EXTENSION_NAME);
return true;
}
bool FFaceTrackingXR::GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions)
{
OutExtensions.Add(XR_METAX1_FACE_TRACKING_VISEMES_EXTENSION_NAME);
return true;
}
const void* FFaceTrackingXR::OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext)
{
if (InModule != nullptr)
{
bExtFaceTrackingSupported = InModule->IsExtensionEnabled(XR_FB_FACE_TRACKING2_EXTENSION_NAME);
bExtFaceTrackingVisemesSupported = InModule->IsExtensionEnabled(XR_METAX1_FACE_TRACKING_VISEMES_EXTENSION_NAME);
}
return InNext;
}
const void* FFaceTrackingXR::OnCreateSession(XrInstance InInstance, XrSystemId InSystem, const void* InNext)
{
InitOpenXRFunctions(InInstance);
OpenXRHMD = (FOpenXRHMD*)GEngine->XRSystem.Get();
return InNext;
}
void FFaceTrackingXR::OnDestroySession(XrSession InSession)
{
OpenXRHMD = nullptr;
}
void* FFaceTrackingXR::OnWaitFrame(XrSession InSession, void* InNext)
{
Update_GameThread(InSession);
return InNext;
}
XrResult FFaceTrackingXR::StartFaceTracking()
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StartFaceTracking] Cannot start face tracking, the instance or session is null."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsFaceTrackingSupported())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StartFaceTracking] Cannot start face tracking, face tracking is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (FaceTracker != XR_NULL_HANDLE)
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StartFaceTracking] Cannot start face tracking, face tracking is already started."));
return XR_SUCCESS;
}
XrFaceTrackerCreateInfo2FB createInfo = { XR_TYPE_FACE_TRACKER_CREATE_INFO2_FB, nullptr };
auto result = XRMovement::xrCreateFaceTracker2FB(OpenXRHMD->GetSession(), &createInfo, &FaceTracker);
if (XR_FAILED(result))
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StartFaceTracking] Failed to start face tracking. Result(%d)"), result);
}
return result;
}
XrResult FFaceTrackingXR::StopFaceTracking()
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StopFaceTracking] Cannot stop face tracking, the instance or session is null."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsFaceTrackingSupported())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StopFaceTracking] Cannot stop face tracking, face tracking is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
XrResult result = XR_SUCCESS;
if (IsFaceTrackingEnabled())
{
result = XRMovement::xrDestroyFaceTracker2FB(FaceTracker);
if (XR_FAILED(result))
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[StopFaceTracking] Failed to stop face tracking. Result(%d)"), result);
}
}
FaceTracker = XR_NULL_HANDLE;
return result;
}
XrResult FFaceTrackingXR::GetCachedFaceState(FOculusXRFaceState& OutState)
{
if (!IsFaceTrackingEnabled())
{
return XR_ERROR_VALIDATION_FAILURE;
}
OutState = CachedFaceState;
return XR_SUCCESS;
}
XrResult FFaceTrackingXR::SetVisemesEnabled(bool enabled)
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[SetVisemesEnabled] Cannot change viseme state, the instance of session is null."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsFaceTrackingVisemesSupported())
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[SetVisemesEnabled] Cannot change viseme state, visemes are not supported."));
return XR_ERROR_VALIDATION_FAILURE;
}
UE_LOG(LogOculusXRMovement, Log, TEXT("[SetVisemesEnabled] Changing visemes enabled state: %hs"), enabled ? "TRUE" : "FALSE");
bVisemesEnabled = enabled;
return XR_SUCCESS;
}
XrResult FFaceTrackingXR::GetCachedVisemeState(FOculusXRFaceVisemesState& OutState)
{
if (!IsFaceTrackingEnabled() || !IsFaceTrackingVisemesEnabled())
{
return XR_ERROR_VALIDATION_FAILURE;
}
OutState = CachedVisemeState;
return XR_SUCCESS;
}
void FFaceTrackingXR::InitOpenXRFunctions(XrInstance InInstance)
{
// XR_FB_Eye_Tracking_Social
OculusXR::XRGetInstanceProcAddr(InInstance, "xrCreateFaceTracker2FB", &xrCreateFaceTracker2FB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrDestroyFaceTracker2FB", &xrDestroyFaceTracker2FB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrGetFaceExpressionWeights2FB", &xrGetFaceExpressionWeights2FB);
}
void FFaceTrackingXR::Update_GameThread(XrSession InSession)
{
check(IsInGameThread());
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession() || !IsFaceTrackingSupported() || !IsFaceTrackingEnabled())
{
return;
}
XrFaceExpressionInfo2FB info{ XR_TYPE_FACE_EXPRESSION_INFO2_FB, nullptr };
info.time = OpenXRHMD->GetDisplayTime();
float weightsArray[XR_FACE_EXPRESSION2_COUNT_FB];
float confidencesArray[XR_FACE_CONFIDENCE2_COUNT_FB];
XrFaceExpressionWeights2FB weights{ XR_TYPE_FACE_EXPRESSION_WEIGHTS2_FB, nullptr };
weights.weights = weightsArray;
weights.weightCount = XR_FACE_EXPRESSION2_COUNT_FB;
weights.confidences = confidencesArray;
weights.confidenceCount = XR_FACE_CONFIDENCE2_COUNT_FB;
bool useVisemes = IsFaceTrackingVisemesSupported() && IsFaceTrackingVisemesEnabled();
XrFaceTrackingVisemesMETAX1 faceTrackingVisemes{ XR_TYPE_FACE_TRACKING_VISEMES_METAX1 };
if (useVisemes)
{
weights.next = &faceTrackingVisemes;
}
auto result = XRMovement::xrGetFaceExpressionWeights2FB(FaceTracker, &info, &weights);
if (XR_FAILED(result))
{
UE_LOG(LogOculusXRMovement, Warning, TEXT("[FaceExpressionStateUpdate] Failed to get face tracking state. Result(%d)"), result);
return;
}
CachedFaceState.bIsValid = (weights.isValid == XR_TRUE);
CachedFaceState.bIsEyeFollowingBlendshapesValid = (weights.isEyeFollowingBlendshapesValid == XR_TRUE);
CachedFaceState.Time = OculusXR::FromXrTime(weights.time);
switch (weights.dataSource)
{
case XR_FACE_TRACKING_DATA_SOURCE2_AUDIO_FB:
CachedFaceState.DataSource = EFaceTrackingDataSource::Audio;
break;
case XR_FACE_TRACKING_DATA_SOURCE2_VISUAL_FB:
CachedFaceState.DataSource = EFaceTrackingDataSource::Visual;
break;
case XR_FACE_TRACKING_DATA_SOURCE_2FB_MAX_ENUM_FB:
CachedFaceState.DataSource = EFaceTrackingDataSource::MAX;
break;
}
FMemory::Memcpy(CachedFaceState.ExpressionWeights.GetData(), weights.weights, XR_FACE_EXPRESSION2_COUNT_FB);
FMemory::Memcpy(CachedFaceState.ExpressionWeightConfidences.GetData(), weights.confidences, XR_FACE_CONFIDENCE2_COUNT_FB);
if (useVisemes)
{
CachedVisemeState.bIsValid = (faceTrackingVisemes.isValid == XR_TRUE);
CachedVisemeState.Time = CachedFaceState.Time;
FMemory::Memcpy(CachedVisemeState.ExpressionVisemeWeights.GetData(), faceTrackingVisemes.visemes, XR_FACE_TRACKING_VISEME_COUNT_METAX1);
}
}
} // namespace XRMovement
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,63 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "OculusXRMovementXRIncludes.h"
#include "IOpenXRExtensionPlugin.h"
#include "OculusXRMovementTypes.h"
#define LOCTEXT_NAMESPACE "OculusXRMovement"
class FOpenXRHMD;
namespace XRMovement
{
extern PFN_xrCreateFaceTracker2FB xrCreateEyeTracker2FB;
extern PFN_xrDestroyFaceTracker2FB xrDestroyEyeTracker2FB;
extern PFN_xrGetFaceExpressionWeights2FB xrGetFaceExpressionWeights2FB;
class FFaceTrackingXR : public IOpenXRExtensionPlugin
{
public:
// IOculusXROpenXRHMDPlugin
virtual bool GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
virtual bool GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
virtual const void* OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext) override;
virtual const void* OnCreateSession(XrInstance InInstance, XrSystemId InSystem, const void* InNext) override;
virtual void OnDestroySession(XrSession InSession) override;
virtual void* OnWaitFrame(XrSession InSession, void* InNext) override;
public:
FFaceTrackingXR();
virtual ~FFaceTrackingXR();
void RegisterAsOpenXRExtension();
bool IsFaceTrackingSupported() const { return bExtFaceTrackingSupported; }
bool IsFaceTrackingEnabled() const { return FaceTracker != XR_NULL_HANDLE; }
bool IsFaceTrackingVisemesSupported() const { return bExtFaceTrackingVisemesSupported; }
bool IsFaceTrackingVisemesEnabled() const { return bVisemesEnabled; }
XrResult StartFaceTracking();
XrResult StopFaceTracking();
XrResult GetCachedFaceState(FOculusXRFaceState& OutState);
XrResult SetVisemesEnabled(bool enabled);
XrResult GetCachedVisemeState(FOculusXRFaceVisemesState& OutState);
private:
void InitOpenXRFunctions(XrInstance InInstance);
void Update_GameThread(XrSession InSession);
bool bExtFaceTrackingSupported;
bool bExtFaceTrackingVisemesSupported;
bool bVisemesEnabled;
FOpenXRHMD* OpenXRHMD;
FOculusXRFaceState CachedFaceState;
FOculusXRFaceVisemesState CachedVisemeState;
XrFaceTracker2FB FaceTracker = XR_NULL_HANDLE;
};
} // namespace XRMovement
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,9 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include <khronos/openxr/openxr.h>
#include <khronos/openxr/meta_openxr_preview/meta_body_tracking_calibration.h>
#include <khronos/openxr/meta_openxr_preview/meta_body_tracking_fidelity.h>
#include <khronos/openxr/meta_openxr_preview/meta_body_tracking_full_body.h>
#include <khronos/openxr/meta_openxr_preview/metax1_face_tracking_visemes.h>