Files
SGBasicDemo/Plugins/SenseGlove/Source/SenseGloveTracking/Private/SGTracking/SGHMDTracker.cpp
T

604 lines
21 KiB
C++

/**
* @file
*
* @author Mamadou Babaei <mamadou@senseglove.com>
*
* @section LICENSE
*
* (The MIT License)
*
* Copyright (c) 2020 - 2025 SenseGlove
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @section DESCRIPTION
*
*
*/
#include "SGTracking/SGHMDTracker.h"
#include "Runtime/Launch/Resources/Version.h"
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
#include "Async/Async.h"
#include "Async/Future.h"
#include "Async/TaskGraphInterfaces.h"
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
#include "Engine/Engine.h"
#include "IHeadMountedDisplay.h"
#include "IXRTrackingSystem.h"
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
#include "Misc/Optional.h"
#include "Misc/Timespan.h"
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
#include "SGSettings/SGSettings.h"
struct FSGHMDTracker::FStaticImpl
{
/************************
* Static methods
************************/
static void GetHMDMonitorInfo(
IHeadMountedDisplay* Device, IHeadMountedDisplay::MonitorInfo& OutMonitorInfo);
/************************
* Constructor / Destructor
************************/
explicit FStaticImpl() = default;
virtual ~FStaticImpl() = delete;
};
void FSGHMDTracker::FStaticImpl::GetHMDMonitorInfo(
IHeadMountedDisplay* Device, IHeadMountedDisplay::MonitorInfo& OutMonitorInfo)
{
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION <= 4
Device->GetHMDMonitorInfo(OutMonitorInfo);
return;
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION <= 4 */
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
TPromise<TOptional<IHeadMountedDisplay::MonitorInfo>> Promise;
const TFuture<TOptional<IHeadMountedDisplay::MonitorInfo>> Future{Promise.GetFuture()};
AsyncTask(ENamedThreads::ActualRenderingThread,
[
Device,
Promise = MoveTemp(Promise)]() mutable
{
IHeadMountedDisplay::MonitorInfo MonitorInfo;
Device->GetHMDMonitorInfo(MonitorInfo);
Promise.SetValue(MoveTemp(MonitorInfo));
});
if (Future.WaitFor(FTimespan::FromMilliseconds(100)))
{
TOptional<IHeadMountedDisplay::MonitorInfo> Result{Future.Get()};
if (Result.IsSet())
{
OutMonitorInfo = Result.GetValue();
}
}
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
}
FName FSGHMDTracker::GetDeviceName()
{
if (GEngine && GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowed())
{
const IHeadMountedDisplay* Device{GEngine->XRSystem->GetHMDDevice()};
if (Device)
{
const FName DeviceName{Device->GetHMDName()};
return DeviceName;
}
}
return NAME_None;
}
bool FSGHMDTracker::IsMetaQuest2()
{
static const FName MetaQuest2DeviceIdentifier{TEXT("Meta Quest 2")};
static const FName OculusQuest2DeviceIdentifier{TEXT("Oculus Quest2")};
static const FString Quest2DeviceIdentifier1{TEXT("Quest 2")};
static const FString Quest2DeviceIdentifier2{TEXT("Quest2")};
const FName DeviceName{GetDeviceName()};
bool bMatched = DeviceName == MetaQuest2DeviceIdentifier || DeviceName == OculusQuest2DeviceIdentifier;
if (!bMatched)
{
const FString DeviceNameString{GetDeviceName().ToString()};
bMatched = DeviceNameString.Contains(Quest2DeviceIdentifier1)
|| DeviceNameString.Contains(Quest2DeviceIdentifier2);
}
return bMatched;
}
bool FSGHMDTracker::IsMetaQuestPro()
{
static const FName MetaQuestProDeviceIdentifier{TEXT("Meta Quest Pro")};
static const FName OculusQuestProDeviceIdentifier{TEXT("Oculus QuestPro")};
static const FString QuestProDeviceIdentifier1{TEXT("Quest Pro")};
static const FString QuestProDeviceIdentifier2{TEXT("QuestPro")};
const FName DeviceName{GetDeviceName()};
bool bMatched = DeviceName == MetaQuestProDeviceIdentifier || DeviceName == OculusQuestProDeviceIdentifier;
if (!bMatched)
{
const FString DeviceNameString{GetDeviceName().ToString()};
bMatched = DeviceNameString.Contains(QuestProDeviceIdentifier1)
|| DeviceNameString.Contains(QuestProDeviceIdentifier2);
}
return bMatched;
}
bool FSGHMDTracker::IsMetaQuest3()
{
static const FName MetaQuest3DeviceIdentifier{TEXT("Meta Quest 3")};
static const FName OculusQuest3DeviceIdentifier{TEXT("Oculus Quest3")};
static const FString Quest3DeviceIdentifier1{TEXT("Quest 3")};
static const FString Quest3DeviceIdentifier2{TEXT("Quest3")};
const FName DeviceName{GetDeviceName()};
bool bMatched = DeviceName == MetaQuest3DeviceIdentifier || DeviceName == OculusQuest3DeviceIdentifier;
if (!bMatched)
{
const FString DeviceNameString{GetDeviceName().ToString()};
bMatched = DeviceNameString.Contains(Quest3DeviceIdentifier1)
|| DeviceNameString.Contains(Quest3DeviceIdentifier2);
}
return bMatched;
}
bool FSGHMDTracker::IsHtcVivePro()
{
#if PLATFORM_ANDROID
return false;
#else /* PLATFORM_ANDROID */
static const FName DeviceIdentifier{TEXT("Vive OpenXR: Vive SRanipal")};
static const FString ViveDeviceIdentifier{TEXT("Vive")};
static const FString SRanipalDeviceIdentifier{TEXT("SRanipal")};
static const FName DeviceIdentifier2{TEXT("SteamVR/OpenXR : lighthouse")};
static const FString LighthouseIdentifier{TEXT("lighthouse")};
const FName DeviceName{GetDeviceName()};
bool bNameMatches = DeviceName == DeviceIdentifier || DeviceName == DeviceIdentifier2;
if (!bNameMatches)
{
const FString DeviceNameString{GetDeviceName().ToString()};
bNameMatches = DeviceNameString.Contains(ViveDeviceIdentifier)
|| DeviceNameString.Contains(SRanipalDeviceIdentifier)
|| DeviceNameString.Contains(LighthouseIdentifier);
}
bool bResolutionMatches = false;
if (bNameMatches)
{
if (GEngine && GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowed())
{
IHeadMountedDisplay* Device{GEngine->XRSystem->GetHMDDevice()};
if (Device)
{
IHeadMountedDisplay::MonitorInfo HmdMonitorInfo;
FStaticImpl::GetHMDMonitorInfo(Device, HmdMonitorInfo);
/// NOTE
// VIVE Pro:
// - Official Spec: 1440 x 1600 pixels per eye (2880 x 1600 pixels combined)
// - Unreal OpenXR Resolution (PCVR): 4936 x 2740
// VIVE Focus 3:
// - Official Spec: 2448 x 2448 pixels per eye (4896 x 2448 pixels combined)
// - Unreal OpenXR Resolution (PCVR): 5016 x 2508
// - Unreal OpenXR Resolution (Standalone): ?
// VIVE XR Elite
// - Official Spec: 1920 x 1920 pixels per eye (3840 x 1920 pixels combined)
// - Unreal OpenXR Resolution (PCVR): 5016 x 2508
// - Unreal OpenXR Resolution (Standalone): ?
// VIVE Focus Vision
// - Official Spec: 2448 x 2448 pixels per eye (4896 x 2448 pixels combined)
// - Unreal OpenXR Resolution (PCVR): 5016 x 2508
// - Unreal OpenXR Resolution (Standalone): 1720 x 1720
if (HmdMonitorInfo.ResolutionX < 5000 && HmdMonitorInfo.ResolutionY > 2700)
{
bResolutionMatches = true;
}
}
}
}
const bool bMatched = bNameMatches && bResolutionMatches;
return bMatched;
#endif /* PLATFORM_ANDROID */
}
bool FSGHMDTracker::IsHtcViveFocus3()
{
#if PLATFORM_ANDROID
static const FName DeviceIdentifier{TEXT("WAVE:SUE")};
const FName DeviceName{GetDeviceName()};
const bool bNameMatches = DeviceName == DeviceIdentifier;
#else /* PLATFORM_ANDROID */
static const FName DeviceIdentifier{TEXT("Vive OpenXR: Vive SRanipal")};
static const FString ViveDeviceIdentifier{TEXT("Vive")};
static const FString SRanipalDeviceIdentifier{TEXT("SRanipal")};
const FName DeviceName{GetDeviceName()};
bool bNameMatches = DeviceName == DeviceIdentifier;
if (!bNameMatches)
{
const FString DeviceNameString{GetDeviceName().ToString()};
bNameMatches = DeviceNameString.Contains(ViveDeviceIdentifier)
|| DeviceNameString.Contains(SRanipalDeviceIdentifier);
}
#endif /* PLATFORM_ANDROID */
/// NOTE
/// For the time being since we cannot distinguish between VIVE Focus Vision, VIVE XR Elite, and VIVE Focus 3 using
/// resolution checks, we skip the resolution checks altogether and assume it's not a VIVE Pro, as VIVE Pro won't
/// come with Android support.
#if PLATFORM_ANDROID
bool bResolutionMatches = true;
#else /* PLATFORM_ANDROID */
bool bResolutionMatches = false;
if (bNameMatches)
{
if (GEngine && GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowed())
{
IHeadMountedDisplay* Device{GEngine->XRSystem->GetHMDDevice()};
if (Device)
{
IHeadMountedDisplay::MonitorInfo HmdMonitorInfo;
FStaticImpl::GetHMDMonitorInfo(Device, HmdMonitorInfo);
/// NOTE
// VIVE Pro:
// - Official Spec: 1440 x 1600 pixels per eye (2880 x 1600 pixels combined)
// - Unreal OpenXR Resolution (PCVR): 4936 x 2740
// VIVE Focus 3:
// - Official Spec: 2448 x 2448 pixels per eye (4896 x 2448 pixels combined)
// - Unreal OpenXR Resolution (PCVR): 5016 x 2508
// - Unreal OpenXR Resolution (Standalone): ?
// VIVE XR Elite
// - Official Spec: 1920 x 1920 pixels per eye (3840 x 1920 pixels combined)
// - Unreal OpenXR Resolution (PCVR): 5016 x 2508
// - Unreal OpenXR Resolution (Standalone): ?
// VIVE Focus Vision
// - Official Spec: 2448 x 2448 pixels per eye (4896 x 2448 pixels combined)
// - Unreal OpenXR Resolution (PCVR): 5016 x 2508
// - Unreal OpenXR Resolution (Standalone): 1720 x 1720
if (HmdMonitorInfo.ResolutionX > 5000 &&
(HmdMonitorInfo.ResolutionY > 2500 && HmdMonitorInfo.ResolutionY < 2700))
{
bResolutionMatches = true;
}
}
}
}
#endif /* PLATFORM_ANDROID */
const bool bMatched = bNameMatches && bResolutionMatches;
return bMatched;
}
bool FSGHMDTracker::IsHtcViveXRElite()
{
#if PLATFORM_ANDROID
static const FName DeviceIdentifier{TEXT("WAVE:SUE")};
const FName DeviceName{GetDeviceName()};
const bool bNameMatches = DeviceName == DeviceIdentifier;
#else /* PLATFORM_ANDROID */
static const FName DeviceIdentifier{TEXT("Vive OpenXR: Vive SRanipal")};
static const FString ViveDeviceIdentifier{TEXT("Vive")};
static const FString SRanipalDeviceIdentifier{TEXT("SRanipal")};
const FName DeviceName{GetDeviceName()};
bool bNameMatches = DeviceName == DeviceIdentifier;
if (!bNameMatches)
{
const FString DeviceNameString{GetDeviceName().ToString()};
bNameMatches = DeviceNameString.Contains(ViveDeviceIdentifier)
|| DeviceNameString.Contains(SRanipalDeviceIdentifier);
}
#endif /* PLATFORM_ANDROID */
/// NOTE
/// For the time being since we cannot distinguish between VIVE Focus Vision, VIVE XR Elite, and VIVE Focus 3 using
/// resolution checks, we skip the resolution checks altogether and assume it's not a VIVE Pro, as VIVE Pro won't
/// come with Android support.
#if PLATFORM_ANDROID
bool bResolutionMatches = true;
#else /* PLATFORM_ANDROID */
bool bResolutionMatches = false;
if (bNameMatches)
{
if (GEngine && GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowed())
{
IHeadMountedDisplay* Device{GEngine->XRSystem->GetHMDDevice()};
if (Device)
{
IHeadMountedDisplay::MonitorInfo HmdMonitorInfo;
FStaticImpl::GetHMDMonitorInfo(Device, HmdMonitorInfo);
/// NOTE
// VIVE Pro:
// - Official Spec: 1440 x 1600 pixels per eye (2880 x 1600 pixels combined)
// - Unreal OpenXR Resolution (PCVR): 4936 x 2740
// VIVE Focus 3:
// - Official Spec: 2448 x 2448 pixels per eye (4896 x 2448 pixels combined)
// - Unreal OpenXR Resolution (PCVR): 5016 x 2508
// - Unreal OpenXR Resolution (Standalone): ?
// VIVE XR Elite
// - Official Spec: 1920 x 1920 pixels per eye (3840 x 1920 pixels combined)
// - Unreal OpenXR Resolution (PCVR): 5016 x 2508
// - Unreal OpenXR Resolution (Standalone): ?
// VIVE Focus Vision
// - Official Spec: 2448 x 2448 pixels per eye (4896 x 2448 pixels combined)
// - Unreal OpenXR Resolution (PCVR): 5016 x 2508
// - Unreal OpenXR Resolution (Standalone): 1720 x 1720
if (HmdMonitorInfo.ResolutionX > 5000 &&
(HmdMonitorInfo.ResolutionY > 2500 && HmdMonitorInfo.ResolutionY < 2700))
{
bResolutionMatches = true;
}
}
}
}
#endif /* PLATFORM_ANDROID */
const bool bMatched = bNameMatches && bResolutionMatches;
return bMatched;
}
bool FSGHMDTracker::IsHtcViveFocusVision()
{
#if PLATFORM_ANDROID
static const FName DeviceIdentifier{TEXT("WAVE:SUE")};
const FName DeviceName{GetDeviceName()};
const bool bNameMatches = DeviceName == DeviceIdentifier;
#else /* PLATFORM_ANDROID */
static const FName DeviceIdentifier{TEXT("Vive OpenXR: Vive SRanipal")};
static const FString ViveDeviceIdentifier{TEXT("Vive")};
static const FString SRanipalDeviceIdentifier{TEXT("SRanipal")};
const FName DeviceName{GetDeviceName()};
bool bNameMatches = DeviceName == DeviceIdentifier;
if (!bNameMatches)
{
const FString DeviceNameString{GetDeviceName().ToString()};
bNameMatches = DeviceNameString.Contains(ViveDeviceIdentifier)
|| DeviceNameString.Contains(SRanipalDeviceIdentifier);
}
#endif /* PLATFORM_ANDROID */
/// NOTE
/// For the time being since we cannot distinguish between VIVE Focus Vision, VIVE XR Elite, and VIVE Focus 3 using
/// resolution checks, we skip the resolution checks altogether and assume it's not a VIVE Pro, as VIVE Pro won't
/// come with Android support.
#if PLATFORM_ANDROID
bool bResolutionMatches = true;
#else /* PLATFORM_ANDROID */
bool bResolutionMatches = false;
if (bNameMatches)
{
if (GEngine && GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowed())
{
IHeadMountedDisplay* Device{GEngine->XRSystem->GetHMDDevice()};
if (Device)
{
IHeadMountedDisplay::MonitorInfo HmdMonitorInfo;
FStaticImpl::GetHMDMonitorInfo(Device, HmdMonitorInfo);
/// NOTE
// VIVE Pro:
// - Official Spec: 1440 x 1600 pixels per eye (2880 x 1600 pixels combined)
// - Unreal OpenXR Resolution (PCVR): 4936 x 2740
// VIVE Focus 3:
// - Official Spec: 2448 x 2448 pixels per eye (4896 x 2448 pixels combined)
// - Unreal OpenXR Resolution (PCVR): 5016 x 2508
// - Unreal OpenXR Resolution (Standalone): ?
// VIVE XR Elite
// - Official Spec: 1920 x 1920 pixels per eye (3840 x 1920 pixels combined)
// - Unreal OpenXR Resolution (PCVR): 5016 x 2508
// - Unreal OpenXR Resolution (Standalone): ?
// VIVE Focus Vision
// - Official Spec: 2448 x 2448 pixels per eye (4896 x 2448 pixels combined)
// - Unreal OpenXR Resolution (PCVR): 5016 x 2508
// - Unreal OpenXR Resolution (Standalone): 1720 x 1720
if (HmdMonitorInfo.ResolutionX > 5000 &&
(HmdMonitorInfo.ResolutionY > 2500 && HmdMonitorInfo.ResolutionY < 2700))
{
bResolutionMatches = true;
}
}
}
}
#endif /* PLATFORM_ANDROID */
const bool bMatched = bNameMatches && bResolutionMatches;
return bMatched;
}
ESGHeadMountedDisplayDevice FSGHMDTracker::GetDeviceType()
{
if (IsMetaQuest2())
{
return ESGHeadMountedDisplayDevice::MetaQuest2;
}
if (IsMetaQuestPro())
{
return ESGHeadMountedDisplayDevice::MetaQuestPro;
}
if (IsMetaQuest3())
{
return ESGHeadMountedDisplayDevice::MetaQuest3;
}
if (IsHtcVivePro())
{
return ESGHeadMountedDisplayDevice::HtcVivePro;
}
const USGSettings* Settings{USGSettings::GetInstance()};
ensureAlwaysMsgf(Settings, TEXT("The settings subsystem has not been initialized!"));
if (Settings->GetTrackingSettings().HMDTrackingSettings.ViveHMDDetectionPriority ==
ESGViveHMDDetectionPriority::FocusVision_XRElite_Focus3)
{
if (IsHtcViveFocusVision())
{
return ESGHeadMountedDisplayDevice::HtcViveFocusVision;
}
if (IsHtcViveXRElite())
{
return ESGHeadMountedDisplayDevice::HtcViveXRElite;
}
if (IsHtcViveFocus3())
{
return ESGHeadMountedDisplayDevice::HtcViveFocus3;
}
}
if (Settings->GetTrackingSettings().HMDTrackingSettings.ViveHMDDetectionPriority ==
ESGViveHMDDetectionPriority::FocusVision_Focus3_XRElite)
{
if (IsHtcViveFocusVision())
{
return ESGHeadMountedDisplayDevice::HtcViveFocusVision;
}
if (IsHtcViveFocus3())
{
return ESGHeadMountedDisplayDevice::HtcViveFocus3;
}
if (IsHtcViveXRElite())
{
return ESGHeadMountedDisplayDevice::HtcViveXRElite;
}
}
if (Settings->GetTrackingSettings().HMDTrackingSettings.ViveHMDDetectionPriority ==
ESGViveHMDDetectionPriority::XRElite_FocusVision_Focus3)
{
if (IsHtcViveXRElite())
{
return ESGHeadMountedDisplayDevice::HtcViveXRElite;
}
if (IsHtcViveFocusVision())
{
return ESGHeadMountedDisplayDevice::HtcViveFocusVision;
}
if (IsHtcViveFocus3())
{
return ESGHeadMountedDisplayDevice::HtcViveFocus3;
}
}
if (Settings->GetTrackingSettings().HMDTrackingSettings.ViveHMDDetectionPriority ==
ESGViveHMDDetectionPriority::XRElite_Focus3_FocusVision)
{
if (IsHtcViveXRElite())
{
return ESGHeadMountedDisplayDevice::HtcViveXRElite;
}
if (IsHtcViveFocus3())
{
return ESGHeadMountedDisplayDevice::HtcViveFocus3;
}
if (IsHtcViveFocusVision())
{
return ESGHeadMountedDisplayDevice::HtcViveFocusVision;
}
}
if (Settings->GetTrackingSettings().HMDTrackingSettings.ViveHMDDetectionPriority ==
ESGViveHMDDetectionPriority::Focus3_FocusVision_XRElite)
{
if (IsHtcViveFocus3())
{
return ESGHeadMountedDisplayDevice::HtcViveFocus3;
}
if (IsHtcViveFocusVision())
{
return ESGHeadMountedDisplayDevice::HtcViveFocusVision;
}
if (IsHtcViveXRElite())
{
return ESGHeadMountedDisplayDevice::HtcViveXRElite;
}
}
if (Settings->GetTrackingSettings().HMDTrackingSettings.ViveHMDDetectionPriority ==
ESGViveHMDDetectionPriority::Focus3_XRElite_FocusVision)
{
if (IsHtcViveFocus3())
{
return ESGHeadMountedDisplayDevice::HtcViveFocus3;
}
if (IsHtcViveXRElite())
{
return ESGHeadMountedDisplayDevice::HtcViveXRElite;
}
if (IsHtcViveFocusVision())
{
return ESGHeadMountedDisplayDevice::HtcViveFocusVision;
}
}
return ESGHeadMountedDisplayDevice::None;
}