mirror of
https://github.com/litruv/TobiiEyetracking.git
synced 2026-07-24 10:46:12 +10:00
Added Tobii from 4.23 + updated
This commit is contained in:
322
Source/TobiiCore/Private/TobiiBlueprintLibrary.cpp
Normal file
322
Source/TobiiCore/Private/TobiiBlueprintLibrary.cpp
Normal file
@@ -0,0 +1,322 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2017- Tobii Technology AB. All rights reserved.
|
||||
*
|
||||
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
|
||||
******************************************************************************/
|
||||
|
||||
#include "TobiiBlueprintLibrary.h"
|
||||
#include "TobiiCoreModule.h"
|
||||
#include "TobiiPlatformSpecific.h"
|
||||
#include "ITobiiCore.h"
|
||||
|
||||
#include "IXRTrackingSystem.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "Misc/ConfigCacheIni.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "Slate/SceneViewport.h"
|
||||
|
||||
UTobiiBlueprintLibrary::UTobiiBlueprintLibrary(const class FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
void UTobiiBlueprintLibrary::SetTobiiEyetrackingEnabled(bool bEyetrackingEnabled)
|
||||
{
|
||||
SetTobiiInt("tobii.EnableEyetracking", bEyetrackingEnabled ? 1 : 0);
|
||||
}
|
||||
|
||||
bool UTobiiBlueprintLibrary::GetTobiiEyetrackingEnabled()
|
||||
{
|
||||
return GetTobiiInt("tobii.EnableEyetracking") != 0;
|
||||
}
|
||||
|
||||
void UTobiiBlueprintLibrary::SetTobiiEyetrackingFrozen(bool bEyetrackingFrozen)
|
||||
{
|
||||
SetTobiiInt("tobii.FreezeGazeData", bEyetrackingFrozen ? 1 : 0);
|
||||
}
|
||||
|
||||
bool UTobiiBlueprintLibrary::GetTobiiEyetrackingFrozen()
|
||||
{
|
||||
return GetTobiiInt("tobii.FreezeGazeData") != 0;
|
||||
}
|
||||
|
||||
FTobiiGazeData UTobiiBlueprintLibrary::GetTobiiCombinedGazeData()
|
||||
{
|
||||
static FTobiiGazeData DummyData;
|
||||
return FTobiiCoreModule::IsAvailable() ? FTobiiCoreModule::GetEyeTracker()->GetCombinedGazeData() : DummyData;
|
||||
}
|
||||
|
||||
FTobiiGazeData UTobiiBlueprintLibrary::GetTobiiLeftGazeData()
|
||||
{
|
||||
static FTobiiGazeData DummyData;
|
||||
return FTobiiCoreModule::IsAvailable() ? FTobiiCoreModule::GetEyeTracker()->GetLeftGazeData() : DummyData;
|
||||
}
|
||||
|
||||
FTobiiGazeData UTobiiBlueprintLibrary::GetTobiiRightGazeData()
|
||||
{
|
||||
static FTobiiGazeData DummyData;
|
||||
return FTobiiCoreModule::IsAvailable() ? FTobiiCoreModule::GetEyeTracker()->GetRightGazeData() : DummyData;
|
||||
}
|
||||
|
||||
FHitResult UTobiiBlueprintLibrary::GetTobiiCombinedWorldGazeHitData()
|
||||
{
|
||||
static FHitResult DummyData;
|
||||
DummyData.Actor = nullptr;
|
||||
return FTobiiCoreModule::IsAvailable() ? FTobiiCoreModule::GetEyeTracker()->GetCombinedWorldGazeHitData() : DummyData;
|
||||
}
|
||||
|
||||
ETobiiGazeTrackerStatus UTobiiBlueprintLibrary::GetTobiiGazeTrackerStatus()
|
||||
{
|
||||
return FTobiiCoreModule::IsAvailable() ? FTobiiCoreModule::GetEyeTracker()->GetGazeTrackerStatus() : ETobiiGazeTrackerStatus::NotConnected;
|
||||
}
|
||||
|
||||
FTobiiDisplayInfo UTobiiBlueprintLibrary::GetTobiiDisplayInformation()
|
||||
{
|
||||
return FTobiiCoreModule::IsAvailable() ? FTobiiCoreModule::GetEyeTracker()->GetDisplayInformation() : FTobiiDisplayInfo();
|
||||
}
|
||||
|
||||
const FTobiiHeadPoseData& UTobiiBlueprintLibrary::GetTobiiHeadPoseData()
|
||||
{
|
||||
static FTobiiHeadPoseData DummyData;
|
||||
return FTobiiCoreModule::IsAvailable() ? FTobiiCoreModule::GetEyeTracker()->GetHeadPoseData() : DummyData;
|
||||
}
|
||||
|
||||
const FTobiiDesktopTrackBox& UTobiiBlueprintLibrary::GetTobiiDesktopTrackBox()
|
||||
{
|
||||
static FTobiiDesktopTrackBox DummyData;
|
||||
return FTobiiCoreModule::IsAvailable() ? FTobiiCoreModule::GetEyeTracker()->GetDesktopTrackBox() : DummyData;
|
||||
}
|
||||
|
||||
FRotator UTobiiBlueprintLibrary::GetTobiiInfiniteScreenAngles()
|
||||
{
|
||||
if (FTobiiCoreModule::IsAvailable())
|
||||
{
|
||||
return ITobiiCore::GetEyeTracker()->GetInfiniteScreenAngles();
|
||||
}
|
||||
else
|
||||
{
|
||||
return FRotator::ZeroRotator;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
/* Utils */
|
||||
/************************************************************************/
|
||||
bool UTobiiBlueprintLibrary::VirtualDesktopPixelToViewportCoordinateUNorm(const FVector2D& VirtualDesktopPixel, FVector2D& OutViewportCoordinateUNorm)
|
||||
{
|
||||
if (!FTobiiCoreModule::IsAvailable() || GEngine == nullptr || GEngine->GameViewport == nullptr || GEngine->GameViewport->GetGameViewport() == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutViewportCoordinateUNorm = GEngine->GameViewport->GetGameViewport()->VirtualDesktopPixelToViewport(FIntPoint(VirtualDesktopPixel.X, VirtualDesktopPixel.Y));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UTobiiBlueprintLibrary::ViewportCoordinateUNormToVirtualDesktopPixel(const FVector2D& ViewportCoordinateUNorm, FVector2D& OutVirtualDesktopPixel)
|
||||
{
|
||||
if (!FTobiiCoreModule::IsAvailable() || GEngine == nullptr || GEngine->GameViewport == nullptr || GEngine->GameViewport->GetGameViewport() == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FTobiiDisplayInfo& DisplayInfo = FTobiiCoreModule::GetEyeTracker()->GetDisplayInformation();
|
||||
FVector2D ScaledInPoint(ViewportCoordinateUNorm.X / DisplayInfo.DpiScale, ViewportCoordinateUNorm.Y / DisplayInfo.DpiScale);
|
||||
OutVirtualDesktopPixel = GEngine->GameViewport->GetGameViewport()->ViewportToVirtualDesktopPixel(ScaledInPoint);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UTobiiBlueprintLibrary::ViewportPixelCoordToCmCoord(const FVector2D& InCoordinatePx, FVector2D& OutCoordinateCm)
|
||||
{
|
||||
const FTobiiDisplayInfo& DisplayInfo = FTobiiCoreModule::GetEyeTracker()->GetDisplayInformation();
|
||||
|
||||
if (DisplayInfo.MainViewportWidthPx > 0.0f && DisplayInfo.MainViewportWidthCm > 0.0f && DisplayInfo.MainViewportHeightPx > 0.0f && DisplayInfo.MainViewportHeightCm > 0.0f)
|
||||
{
|
||||
OutCoordinateCm.Set(InCoordinatePx.X / DisplayInfo.MainViewportWidthPx * DisplayInfo.MainViewportWidthCm, InCoordinatePx.Y / DisplayInfo.MainViewportHeightPx * DisplayInfo.MainViewportHeightCm);
|
||||
return true;
|
||||
}
|
||||
|
||||
OutCoordinateCm = FVector2D::ZeroVector;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UTobiiBlueprintLibrary::ViewportCmCoordToPixelCoord(const FVector2D& InCoordinateCm, FVector2D& OutCoordinatePx)
|
||||
{
|
||||
const FTobiiDisplayInfo& DisplayInfo = FTobiiCoreModule::GetEyeTracker()->GetDisplayInformation();
|
||||
|
||||
if (DisplayInfo.MainViewportWidthPx > 0.0f && DisplayInfo.MainViewportWidthCm > 0.0f && DisplayInfo.MainViewportHeightPx > 0.0f && DisplayInfo.MainViewportHeightCm > 0.0f)
|
||||
{
|
||||
OutCoordinatePx.Set(InCoordinateCm.X / DisplayInfo.MainViewportWidthCm * DisplayInfo.MainViewportWidthPx, InCoordinateCm.Y / DisplayInfo.MainViewportHeightCm * DisplayInfo.MainViewportHeightPx);
|
||||
return true;
|
||||
}
|
||||
|
||||
OutCoordinatePx = FVector2D::ZeroVector;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UTobiiBlueprintLibrary::ViewportPixelCoordToUNormCoord(const FVector2D& InCoordinatePx, FVector2D& OutCoordinateUNorm)
|
||||
{
|
||||
const FTobiiDisplayInfo& DisplayInfo = FTobiiCoreModule::GetEyeTracker()->GetDisplayInformation();
|
||||
|
||||
if (DisplayInfo.MainViewportWidthPx > 0.0f && DisplayInfo.MainViewportHeightPx > 0.0f)
|
||||
{
|
||||
OutCoordinateUNorm.Set(InCoordinatePx.X / DisplayInfo.MainViewportWidthPx, InCoordinatePx.Y / DisplayInfo.MainViewportHeightPx);
|
||||
return true;
|
||||
}
|
||||
|
||||
OutCoordinateUNorm = FVector2D::ZeroVector;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UTobiiBlueprintLibrary::ViewportUNormCoordToPixelCoord(const FVector2D& InCoordinateUNorm, FVector2D& OutCoordinatePx)
|
||||
{
|
||||
const FTobiiDisplayInfo& DisplayInfo = FTobiiCoreModule::GetEyeTracker()->GetDisplayInformation();
|
||||
|
||||
if (DisplayInfo.MainViewportWidthPx > 0.0f && DisplayInfo.MainViewportHeightPx > 0.0f)
|
||||
{
|
||||
OutCoordinatePx.Set(InCoordinateUNorm.X * DisplayInfo.MainViewportWidthPx, InCoordinateUNorm.Y * DisplayInfo.MainViewportHeightPx);
|
||||
return true;
|
||||
}
|
||||
|
||||
OutCoordinatePx = FVector2D::ZeroVector;
|
||||
return false;
|
||||
}
|
||||
|
||||
// internal helper
|
||||
FQuat GetHMDToWorldRotation(APlayerController* PlayerController)
|
||||
{
|
||||
FQuat ToWorld = FQuat::Identity;
|
||||
FVector HMDPosition;
|
||||
bool valid = GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, ToWorld, HMDPosition);
|
||||
|
||||
const bool IsEmulated = IConsoleManager::Get().FindConsoleVariable(TEXT("tobii.emulation.EnableEyetrackingEmulation"))->GetInt() != 0;
|
||||
const bool IsApplyControlRotation = IConsoleManager::Get().FindConsoleVariable(TEXT("tobii.xr.ApplyControlRotation"))->GetInt() != 0;
|
||||
const bool IsApplyActorRotation = IConsoleManager::Get().FindConsoleVariable(TEXT("tobii.xr.ApplyActorRotation"))->GetInt() != 0;
|
||||
|
||||
if (!IsEmulated)
|
||||
{
|
||||
if (IsApplyControlRotation)
|
||||
{
|
||||
FVector RealWorldUp = ToWorld.Inverse().RotateVector(FVector::UpVector);
|
||||
FQuat YawRotation = FQuat(RealWorldUp, FMath::DegreesToRadians(PlayerController->GetControlRotation().Yaw));
|
||||
ToWorld *= YawRotation;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsApplyActorRotation && PlayerController != nullptr && PlayerController->GetPawn() != nullptr)
|
||||
{
|
||||
ToWorld *= PlayerController->GetPawn()->GetActorRotation().Quaternion();
|
||||
}
|
||||
|
||||
return ToWorld;
|
||||
}
|
||||
|
||||
int32 UTobiiBlueprintLibrary::GetTobiiInt(FString CVarName)
|
||||
{
|
||||
const auto CVar = IConsoleManager::Get().FindConsoleVariable(*CVarName);
|
||||
return (CVar != nullptr) ? CVar->GetInt() : 0;
|
||||
}
|
||||
void UTobiiBlueprintLibrary::SetTobiiInt(FString CVarName, const int32 NewValue)
|
||||
{
|
||||
const auto CVar = IConsoleManager::Get().FindConsoleVariable(*CVarName);
|
||||
if (CVar != nullptr)
|
||||
{
|
||||
CVar->Set(NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
float UTobiiBlueprintLibrary::GetTobiiFloat(FString CVarName)
|
||||
{
|
||||
const auto CVar = IConsoleManager::Get().FindConsoleVariable(*CVarName);
|
||||
return (CVar != nullptr) ? CVar->GetFloat() : 0.0f;
|
||||
}
|
||||
void UTobiiBlueprintLibrary::SetTobiiFloat(FString CVarName, const float NewValue)
|
||||
{
|
||||
const auto CVar = IConsoleManager::Get().FindConsoleVariable(*CVarName);
|
||||
if (CVar != nullptr)
|
||||
{
|
||||
CVar->Set(NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
FString GetTobiiSettingsFilePath()
|
||||
{
|
||||
return FPaths::Combine(FPaths::GeneratedConfigDir() + ANSI_TO_TCHAR(FPlatformProperties::PlatformName()), "TobiiEyetracking.ini");
|
||||
}
|
||||
void UTobiiBlueprintLibrary::LoadTobiiSettings()
|
||||
{
|
||||
ApplyCVarSettingsFromIni(TEXT("TobiiEyetracking"), *GetTobiiSettingsFilePath(), ECVF_SetByConstructor);
|
||||
}
|
||||
void UTobiiBlueprintLibrary::SaveTobiiSetting(FString CVarSettingToSave)
|
||||
{
|
||||
auto CVar = IConsoleManager::Get().FindConsoleVariable(*CVarSettingToSave);
|
||||
if (CVar != nullptr)
|
||||
{
|
||||
GConfig->EmptySection(TEXT("TobiiEyetracking"), *GetTobiiSettingsFilePath());
|
||||
GConfig->SetString(TEXT("TobiiEyetracking"), *CVarSettingToSave, *CVar->GetString(), *GetTobiiSettingsFilePath());
|
||||
GConfig->Flush(false, *GetTobiiSettingsFilePath());
|
||||
}
|
||||
}
|
||||
|
||||
bool UTobiiBlueprintLibrary::IsXRPlayerController(const APlayerController* PlayerController)
|
||||
{
|
||||
if (GEngine != nullptr)
|
||||
{
|
||||
if (PlayerController != nullptr)
|
||||
{
|
||||
ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(PlayerController->Player);
|
||||
if (LocalPlayer != nullptr && LocalPlayer->ViewportClient != nullptr)
|
||||
{
|
||||
//@TODO: Bugged in Shipping builds 4.18? Returns false even though running through VIVE.
|
||||
return GEngine->IsStereoscopic3D(LocalPlayer->ViewportClient->Viewport);
|
||||
}
|
||||
}
|
||||
|
||||
return GEngine->IsStereoscopic3D();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
FVector UTobiiBlueprintLibrary::ConvertGazeToUserSpace(APlayerController* PlayerController, const FVector& WorldSpaceGazeDirection)
|
||||
{
|
||||
FVector LocalSpaceDirection = WorldSpaceGazeDirection;
|
||||
if (GEngine == nullptr || PlayerController == nullptr)
|
||||
{
|
||||
return LocalSpaceDirection;
|
||||
}
|
||||
|
||||
if (UTobiiBlueprintLibrary::IsXRPlayerController(PlayerController))
|
||||
{
|
||||
static const auto HMDOrientationCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("tobii.xr.ApplyHMDOrientation"));
|
||||
static const auto ActorRotationCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("tobii.xr.ApplyActorRotation"));
|
||||
|
||||
if (ActorRotationCVar->GetInt() != 0
|
||||
&& PlayerController->GetPawn() != nullptr)
|
||||
{
|
||||
FQuat ActorRotation = PlayerController->GetPawn()->GetActorRotation().Quaternion();
|
||||
LocalSpaceDirection = ActorRotation.UnrotateVector(LocalSpaceDirection);
|
||||
}
|
||||
|
||||
if (HMDOrientationCVar->GetInt() != 0)
|
||||
{
|
||||
FQuat HMDOrientation;
|
||||
FVector HMDPosition;
|
||||
GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, HMDOrientation, HMDPosition);
|
||||
LocalSpaceDirection = HMDOrientation.UnrotateVector(LocalSpaceDirection);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (PlayerController->PlayerCameraManager != nullptr)
|
||||
{
|
||||
FRotator CameraRotation = PlayerController->PlayerCameraManager->GetCameraRotation();
|
||||
LocalSpaceDirection = CameraRotation.UnrotateVector(LocalSpaceDirection);
|
||||
}
|
||||
}
|
||||
|
||||
return LocalSpaceDirection;
|
||||
}
|
||||
84
Source/TobiiCore/Private/TobiiCoreModule.cpp
Normal file
84
Source/TobiiCore/Private/TobiiCoreModule.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2017- Tobii Technology AB. All rights reserved.
|
||||
*
|
||||
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
|
||||
******************************************************************************/
|
||||
|
||||
#include "TobiiCoreModule.h"
|
||||
|
||||
#include "UserGuide/TobiiEditorExtension.h"
|
||||
|
||||
#include "IEyeTrackerModule.h"
|
||||
#include "GameFramework/HUD.h"
|
||||
#include "Misc/Paths.h"
|
||||
|
||||
IMPLEMENT_MODULE(FTobiiCoreModule, TobiiCore)
|
||||
|
||||
/************************************************************************/
|
||||
/* FTobiiCoreModule */
|
||||
/************************************************************************/
|
||||
void FTobiiCoreModule::StartupModule()
|
||||
{
|
||||
EyeTracker.Reset();
|
||||
|
||||
#if TOBII_EYETRACKING_ACTIVE
|
||||
FString RelativeGICDllPath = FString(TEXT(TOBII_GIC_RELATIVE_DLL_PATH));
|
||||
|
||||
#if TOBII_COMPILE_AS_ENGINE_PLUGIN
|
||||
FString FullGICDllPath = FPaths::Combine(FPaths::EngineDir(), RelativeGICDllPath);
|
||||
#else
|
||||
FString FullGICDllPath = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectPluginsDir(), RelativeGICDllPath));
|
||||
#endif //TOBII_COMPILE_AS_ENGINE_PLUGIN
|
||||
|
||||
GICDllHandle = FPlatformProcess::GetDllHandle(*FullGICDllPath);
|
||||
|
||||
if (GICDllHandle != nullptr)
|
||||
{
|
||||
FTobiiEyeTracker* NewEyeTracker = new FTobiiEyeTracker();
|
||||
if (NewEyeTracker != nullptr && NewEyeTracker->IsConnectedToEyeTracker())
|
||||
{
|
||||
IModularFeatures::Get().RegisterModularFeature(GetModularFeatureName(), this);
|
||||
EyeTracker = TSharedPtr<ITobiiEyeTracker, ESPMode::ThreadSafe>(NewEyeTracker);
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
EditorExtensions = new FTobiiEditorExtension(this);
|
||||
#endif //WITH_EDITOR
|
||||
}
|
||||
#endif //TOBII_EYETRACKING_ACTIVE
|
||||
}
|
||||
|
||||
void FTobiiCoreModule::ShutdownModule()
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
if (EditorExtensions != nullptr)
|
||||
{
|
||||
delete EditorExtensions;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (EyeTracker.IsValid())
|
||||
{
|
||||
IModularFeatures::Get().UnregisterModularFeature(GetModularFeatureName(), this);
|
||||
|
||||
FTobiiEyeTracker* EyetrackerPtr = (FTobiiEyeTracker*)EyeTracker.Get();
|
||||
EyetrackerPtr->Shutdown();
|
||||
EyeTracker.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
TSharedPtr<IEyeTracker, ESPMode::ThreadSafe> FTobiiCoreModule::CreateEyeTracker()
|
||||
{
|
||||
return StaticCastSharedPtr<IEyeTracker, ITobiiEyeTracker, ESPMode::ThreadSafe>(EyeTracker);
|
||||
}
|
||||
|
||||
bool FTobiiCoreModule::IsEyeTrackerConnected() const
|
||||
{
|
||||
bool bSRanipalRunning = FModuleManager::Get().IsModuleLoaded("SRanipalCore") && FModuleManager::Get().GetModuleChecked<IEyeTrackerModule>("SRanipalCore").IsEyeTrackerConnected();
|
||||
return EyeTracker.IsValid() && !bSRanipalRunning;
|
||||
}
|
||||
|
||||
TSharedPtr<ITobiiEyeTracker, ESPMode::ThreadSafe> FTobiiCoreModule::GetEyeTrackerInternal()
|
||||
{
|
||||
return EyeTracker;
|
||||
}
|
||||
41
Source/TobiiCore/Private/TobiiCoreModule.h
Normal file
41
Source/TobiiCore/Private/TobiiCoreModule.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2017- Tobii Technology AB. All rights reserved.
|
||||
*
|
||||
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IInputDevice.h"
|
||||
#include "ITobiiCore.h"
|
||||
#include "TobiiEyetracker.h"
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "DisplayDebugHelpers.h"
|
||||
|
||||
class AHUD;
|
||||
class UCanvas;
|
||||
|
||||
class FTobiiCoreModule : public ITobiiCore
|
||||
{
|
||||
/************************************************************************/
|
||||
/* ITobiiCore */
|
||||
/************************************************************************/
|
||||
public:
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
virtual TSharedPtr<class IEyeTracker, ESPMode::ThreadSafe> CreateEyeTracker() override;
|
||||
virtual bool IsEyeTrackerConnected() const override;
|
||||
|
||||
protected:
|
||||
virtual TSharedPtr<ITobiiEyeTracker, ESPMode::ThreadSafe> GetEyeTrackerInternal() override;
|
||||
|
||||
private:
|
||||
TSharedPtr<ITobiiEyeTracker, ESPMode::ThreadSafe> EyeTracker;
|
||||
void* GICDllHandle;
|
||||
|
||||
#if WITH_EDITOR
|
||||
class FTobiiEditorExtension* EditorExtensions;
|
||||
#endif
|
||||
};
|
||||
929
Source/TobiiCore/Private/TobiiEyetracker.cpp
Normal file
929
Source/TobiiCore/Private/TobiiEyetracker.cpp
Normal file
@@ -0,0 +1,929 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2017- Tobii Technology AB. All rights reserved.
|
||||
*
|
||||
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
|
||||
******************************************************************************/
|
||||
|
||||
#include "TobiiEyetracker.h"
|
||||
#include "TobiiBlueprintLibrary.h"
|
||||
|
||||
#include "DrawDebugHelpers.h"
|
||||
#include "IXRTrackingSystem.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Engine/Canvas.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
#include "GameFramework/HUD.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "Slate/SceneViewport.h"
|
||||
#include "Widgets/SWindow.h"
|
||||
#include "Misc/App.h"
|
||||
|
||||
#include "tobii_gameintegration.h"
|
||||
|
||||
/************************************************************************/
|
||||
/* Configuration CVars */
|
||||
/************************************************************************/
|
||||
static TAutoConsoleVariable<int32> CVarTobiiEnableEyetracking(TEXT("tobii.EnableEyetracking"), 1, TEXT("0 - Eyetracking is disabled. 1 - Eyetracking is enabled."));
|
||||
static TAutoConsoleVariable<int32> CVarTobiiFreezingGazeData(TEXT("tobii.FreezeGazeData"), 0, TEXT("0 - Eyetracking data is not frozen. 1 - Eyetracking data is frozen. This is really useful for visualizing gaze and debugging."));
|
||||
static TAutoConsoleVariable<int32> CVarTobiiFocusTraceChannel(TEXT("tobii.FocusTraceChannel"), (int32)ECC_Visibility, TEXT("This is the trace channel that will be used in all eye tracking queries."));
|
||||
static TAutoConsoleVariable<float> CVarTobiiFovealConeAngleDegrees(TEXT("tobii.FovealConeAngleDegrees"), 5.0f, TEXT("A larger value here will lead to the GTOM system considering a larger area around the gaze point. Refer to this link to see what values are reasonable: https://en.wikipedia.org/wiki/Fovea_centralis#/media/File:Macula.svg. Eventually, we will hopefully select good values for you so you don't have to care about this."));
|
||||
static TAutoConsoleVariable<float> CVarTobiiMaximumTraceDistance(TEXT("tobii.MaximumTraceDistance"), 5000.0f, TEXT("This is how far in front of the player we can detect objects. This could impact game play so be careful. Shorter range can also positively impact performance."));
|
||||
static TAutoConsoleVariable<float> CVarTobiiMaximumAcceptableStableGazeAverageAngularSpeedDegPerMicroSecs(TEXT("tobii.MaximumAcceptableStableGazeAverageAngularSpeedDegPerMicroSecs"), 0.00001f, TEXT("Raising this value will make the system more generous when determining if a gaze point is currently stable or not."));
|
||||
static TAutoConsoleVariable<float> CVarTobiiStablePointInterpolationBias(TEXT("tobii.StablePointInterpolationBias"), 0.6f, TEXT("Gaze point stability is based on a linear interpolation filter. This scalar determines the bias given to the new point."));
|
||||
|
||||
static TAutoConsoleVariable<float> CVarTobiiWatchingToNotWatchingInertiaSecs(TEXT("tobii.desktop.WatchingToNotWatchingInertiaSecs"), 0.6f, TEXT("To make the experience more stable we introduce inertia for transitioning from the UserPresentAndWatchingWindow eyetracking status state. This is the length of that inertia period in seconds. Please note that the eyetracker also has innate inertia, this is just a way to further control it."));
|
||||
static TAutoConsoleVariable<float> CVarTobiiAdditionalWindowMarginCm(TEXT("tobii.desktop.AdditionalWindowMarginCm"), 1.3f, TEXT("We add an artificial margin around the game window to avoid precision and accuracy problems when the gaze point is close to the edge of the screen. The value is the size of this extra border in centimeters (same as unreal units)."));
|
||||
static TAutoConsoleVariable<float> CVarTobiiHeadPosePositionInterpolationBias(TEXT("tobii.desktop.HeadPosePositionInterpolationBias"), 0.6f, TEXT("Head pose position filtration is done using a linear interpolation filter. This scalar determines the bias given to the new position data."));
|
||||
static TAutoConsoleVariable<float> CVarTobiiHeadPoseRotationInterpolationBias(TEXT("tobii.desktop.HeadPoseRotationInterpolationBias"), 0.1f, TEXT("Head pose rotation filtration is done using a spherical linear interpolation filter. This scalar is used as the interpolation constant."));
|
||||
static TAutoConsoleVariable<int32> CVarInfiniteScreenEnabled(TEXT("tobii.desktop.InfiniteScreenEnabled"), 1, TEXT("0 - Extended View is off. 1 - Extended View is on."));
|
||||
static TAutoConsoleVariable<float> CVarExtendedViewMaxGazeAngleDeg(TEXT("tobii.desktop.ExtendedViewMaxGazeAngleDeg"), 30, TEXT("Extended View angles will not rotate the camera more than this amount of degrees due to the user's gaze."));
|
||||
static TAutoConsoleVariable<float> CVarExtendedViewHeadSensitivity(TEXT("tobii.desktop.ExtendedViewHeadSensitivity"), 0.5f, TEXT("This is how sensitive the head component of extended view will be."));
|
||||
static TAutoConsoleVariable<float> CVarExtendedViewGazeSensitivity(TEXT("tobii.desktop.ExtendedViewGazeSensitivity"), 0.5f, TEXT("This is how sensitive the gaze component of extended view will be."));
|
||||
static TAutoConsoleVariable<float> CVarExtendedViewGazeOnlyScalar(TEXT("tobii.desktop.ExtendedViewGazeOnlyScalar"), 1.3f, TEXT("This is how we will scale the gaze extended view if only gaze is available."));
|
||||
static TAutoConsoleVariable<float> CVarExtendedViewHeadOnlyScalar(TEXT("tobii.desktop.ExtendedViewHeadOnlyScalar"), 1.3f, TEXT("This is how we will scale the head extended view if only head is available."));
|
||||
|
||||
static TAutoConsoleVariable<float> CVarHMDScreenDistanceToEyeCm(TEXT("tobii.xr.HMDScreenDistanceToEyeCm"), 3.0f, TEXT("Since the foveal cone on the screen depends on the distance of the eye from the screen, we need this. Since UE4 doesn't provide APIs to poll it dynamically, you have to set it manually here instead."));
|
||||
static TAutoConsoleVariable<int32> CVarTobiiApplyHMDOrientation(TEXT("tobii.xr.ApplyHMDOrientation"), 1, TEXT("Apply the HMD orientation to your gaze direction"));
|
||||
static TAutoConsoleVariable<int32> CVarTobiiApplyActorRotation(TEXT("tobii.xr.ApplyActorRotation"), 1, TEXT("Apply your actor rotation to your gaze direction"));
|
||||
|
||||
static TAutoConsoleVariable<int32> CVarEnableEyetrackingEmulation(TEXT("tobii.emulation.EnableEyetrackingEmulation"), 0, TEXT("0 - Don't emulate eye tracking. 1 - Emulate eye tracking."));
|
||||
static TAutoConsoleVariable<float> CVarEyetrackingEmulationGazeSpeed(TEXT("tobii.emulation.DesktopEyetrackingEmulationGazeSpeed"), 0.4f, TEXT("When emulating gaze on desktop, this is the speed scalar used for the gaze point."));
|
||||
|
||||
static TAutoConsoleVariable<int32> CVarEnableEyetrackingDebug(TEXT("tobii.debug"), 0, TEXT("0 - Eyetracking debug visualizations are disabled. 1 - Eyetracking debug visualizations are enabled."));
|
||||
static TAutoConsoleVariable<int32> CVarEnableGazePointDebug(TEXT("tobii.debug.EnableGazePointDebug"), 1, TEXT("0 - Gaze point debug visualizations are disabled. 1 - Gaze point debug visualizations are enabled."));
|
||||
static TAutoConsoleVariable<int32> CVarEnableHeadPoseDebug(TEXT("tobii.debug.EnableHeadPoseDebug"), 0, TEXT("0 - Head pose debug visualizations are disabled. 1 - Head pose debug visualizations are enabled."));
|
||||
|
||||
#if TOBII_EYETRACKING_ACTIVE
|
||||
|
||||
using namespace TobiiGameIntegration;
|
||||
|
||||
FTobiiEyeTracker::FTobiiEyeTracker()
|
||||
: TgiApi(nullptr)
|
||||
, ActivePlayerController(nullptr)
|
||||
, bIsXR(false)
|
||||
{
|
||||
TgiApi = GetApi(TCHAR_TO_ANSI(FApp::GetProjectName()));
|
||||
StartTime = FDateTime::UtcNow();
|
||||
ResetData();
|
||||
}
|
||||
|
||||
FTobiiEyeTracker::~FTobiiEyeTracker()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
void FTobiiEyeTracker::Shutdown()
|
||||
{
|
||||
if (TgiApi != nullptr)
|
||||
{
|
||||
TgiApi->Shutdown();
|
||||
TgiApi = nullptr;
|
||||
}
|
||||
|
||||
GazeTrackerStatus = ETobiiGazeTrackerStatus::NotConnected;
|
||||
}
|
||||
|
||||
bool FTobiiEyeTracker::IsConnectedToEyeTracker()
|
||||
{
|
||||
if (TgiApi != nullptr)
|
||||
{
|
||||
IStreamsProvider* StreamsProvider = TgiApi->GetStreamsProvider();
|
||||
ITrackerController* TrackerController = TgiApi->GetTrackerController();
|
||||
if (TrackerController != nullptr && StreamsProvider != nullptr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FTobiiEyeTracker::ResetData()
|
||||
{
|
||||
ActivePlayerController.Reset();
|
||||
CombinedWorldGazeHitData = FHitResult();
|
||||
LeftWorldGazeHitData= FHitResult();
|
||||
RightWorldGazeHitData = FHitResult();
|
||||
LeftGazeData = FTobiiGazeData();
|
||||
RightGazeData = FTobiiGazeData();
|
||||
CombinedGazeData = FTobiiGazeData();
|
||||
GazeTrackerStatus = ETobiiGazeTrackerStatus::NotConnected;
|
||||
HeadPoseData = FTobiiHeadPoseData();
|
||||
DisplayInfo = FTobiiDisplayInfo();
|
||||
|
||||
GazePointDeltaTimeMicroSecs = 0;
|
||||
CurrentAverageGazeAngularSpeedDegPerMicroSecs = 0.0;
|
||||
}
|
||||
|
||||
bool FTobiiEyeTracker::Tick(float DeltaTime)
|
||||
{
|
||||
QUICK_SCOPE_CYCLE_COUNTER(STAT_TobiiEyetracking_Tick);
|
||||
|
||||
if (GEngine->EyeTrackingDevice.Get() != this)
|
||||
{
|
||||
Shutdown();
|
||||
return true;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Update status
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
if (!CVarTobiiEnableEyetracking.GetValueOnGameThread())
|
||||
{
|
||||
if (GazeTrackerStatus != ETobiiGazeTrackerStatus::Disabled)
|
||||
{
|
||||
ResetData();
|
||||
GazeTrackerStatus = ETobiiGazeTrackerStatus::Disabled;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (GEngine == nullptr
|
||||
|| GEngine->GameViewport == nullptr
|
||||
|| GEngine->GameViewport->GetGameViewport() == nullptr
|
||||
|| TgiApi == nullptr)
|
||||
{
|
||||
ResetData();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!UpdateLowLevelResources())
|
||||
{
|
||||
ResetData();
|
||||
return true;
|
||||
}
|
||||
|
||||
//If we have no active PC, set default
|
||||
if (!ActivePlayerController.IsValid())
|
||||
{
|
||||
SetEyeTrackedPlayer(nullptr);
|
||||
}
|
||||
|
||||
if (!CVarTobiiFreezingGazeData.GetValueOnGameThread())
|
||||
{
|
||||
if (bIsXR)
|
||||
{
|
||||
TickXR(DeltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
TickDesktop(DeltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateWorldSpaceData(DeltaTime);
|
||||
UpdateStabilityData(DeltaTime);
|
||||
|
||||
if (ActivePlayerController.IsValid()
|
||||
&& ActivePlayerController->GetWorld() != nullptr
|
||||
&& CVarEnableEyetrackingDebug.GetValueOnGameThread())
|
||||
{
|
||||
if (CVarEnableGazePointDebug.GetValueOnGameThread())
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("Gaze Direction: (%.2f, %.2f, %.2f)"), CombinedGazeData.WorldGazeDirection.X, CombinedGazeData.WorldGazeDirection.Y, CombinedGazeData.WorldGazeDirection.Z);
|
||||
const float DrawSize = FVector::Dist(CombinedGazeData.WorldGazeOrigin, CombinedWorldGazeHitData.Location) * FMath::Tan(FMath::DegreesToRadians(CombinedGazeData.WorldGazeConeAngleDegrees));
|
||||
DrawDebugSphere(ActivePlayerController->GetWorld(), CombinedWorldGazeHitData.Location, DrawSize, 16, CombinedGazeData.bIsStable ? FColor::Green : FColor::Red, false, 0.0f);
|
||||
}
|
||||
|
||||
if (CVarEnableHeadPoseDebug.GetValueOnGameThread())
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("Head location: (%.2f, %.2f, %.2f) ~~ Head orientation: (%.2f, %.2f, %.2f)")
|
||||
, HeadPoseData.HeadLocation.X, HeadPoseData.HeadLocation.Y, HeadPoseData.HeadLocation.Z
|
||||
, HeadPoseData.HeadOrientation.Yaw, HeadPoseData.HeadOrientation.Pitch, HeadPoseData.HeadOrientation.Roll);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FTobiiEyeTracker::UpdateLowLevelResources()
|
||||
{
|
||||
//Get game window and monitor handles
|
||||
if (DisplayInfo.GameMonitorHandle == nullptr || PlatformNotifications.bShouldUpdateGameMonitorHandle)
|
||||
{
|
||||
PlatformNotifications.bShouldUpdateGameMonitorHandle = false;
|
||||
|
||||
if (!GEngine->GameViewport->GetWindow().IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
SWindow* GameWindow = GEngine->GameViewport->GetWindow().Get();
|
||||
if (!GameWindow->GetNativeWindow().IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DisplayInfo.DpiScale = GameWindow->GetCachedGeometry().Scale;
|
||||
DisplayInfo.GameWindowHandle = GameWindow->GetNativeWindow().Get()->GetOSWindowHandle();
|
||||
if (DisplayInfo.GameWindowHandle == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DisplayInfo.GameMonitorHandle = FTobiiPlatformSpecific::GetMonitorInformation(DisplayInfo.GameWindowHandle, DisplayInfo.MonitorWidthPx, DisplayInfo.MonitorHeightPx);
|
||||
}
|
||||
|
||||
//Since the viewport size can change every frame, we cannot treat this data as constant.
|
||||
IStreamsProvider* StreamsProvider = TgiApi->GetStreamsProvider();
|
||||
if (StreamsProvider != nullptr)
|
||||
{
|
||||
GazePoint MaxGazeUNorm, MaxGazeMm;
|
||||
MaxGazeUNorm.X = MaxGazeUNorm.Y = 1.0f;
|
||||
StreamsProvider->ConvertGazePoint(MaxGazeUNorm, MaxGazeMm, Normalized, Mm);
|
||||
|
||||
DisplayInfo.MonitorWidthCm = MaxGazeMm.X / 10.0f;
|
||||
DisplayInfo.MonitorHeightCm = MaxGazeMm.Y / 10.0f;
|
||||
|
||||
FVector2D ViewportSize;
|
||||
GEngine->GameViewport->GetViewportSize(ViewportSize);
|
||||
DisplayInfo.MainViewportWidthPx = ViewportSize.X;
|
||||
DisplayInfo.MainViewportHeightPx = ViewportSize.Y;
|
||||
DisplayInfo.MainViewportWidthCm = (((float)DisplayInfo.MainViewportWidthPx / (float)DisplayInfo.MonitorWidthPx) * DisplayInfo.MonitorWidthCm);
|
||||
DisplayInfo.MainViewportHeightCm = (((float)DisplayInfo.MainViewportHeightPx / (float)DisplayInfo.MonitorHeightPx) * DisplayInfo.MonitorHeightCm);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FVector2D& FTobiiEyeTracker::TickEmulatedGazePointUNorm(float DeltaTime)
|
||||
{
|
||||
static FVector2D EmulatedGazeNorm(0.5f, 0.5f);
|
||||
|
||||
if (ActivePlayerController.IsValid())
|
||||
{
|
||||
const float EmulatedGazeSpeed = CVarEyetrackingEmulationGazeSpeed.GetValueOnGameThread();
|
||||
if (ActivePlayerController->IsInputKeyDown(FKey("Up")))
|
||||
{
|
||||
EmulatedGazeNorm.Y -= DeltaTime * EmulatedGazeSpeed;
|
||||
}
|
||||
else if (ActivePlayerController->IsInputKeyDown(FKey("Down")))
|
||||
{
|
||||
EmulatedGazeNorm.Y += DeltaTime * EmulatedGazeSpeed;
|
||||
}
|
||||
|
||||
if (ActivePlayerController->IsInputKeyDown(FKey("Left")))
|
||||
{
|
||||
EmulatedGazeNorm.X -= DeltaTime * EmulatedGazeSpeed;
|
||||
}
|
||||
else if (ActivePlayerController->IsInputKeyDown(FKey("Right")))
|
||||
{
|
||||
EmulatedGazeNorm.X += DeltaTime * EmulatedGazeSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
EmulatedGazeNorm.X = FMath::Clamp(EmulatedGazeNorm.X, 0.0f, 1.0f);
|
||||
EmulatedGazeNorm.Y = FMath::Clamp(EmulatedGazeNorm.Y, 0.0f, 1.0f);
|
||||
|
||||
return EmulatedGazeNorm;
|
||||
}
|
||||
|
||||
void FTobiiEyeTracker::TickDesktop(float DeltaTime)
|
||||
{
|
||||
//Pump API
|
||||
ITrackerController* TrackerController = TgiApi->GetTrackerController();
|
||||
if (TrackerController != nullptr)
|
||||
{
|
||||
TrackerController->TrackWindow(DisplayInfo.GameWindowHandle);
|
||||
}
|
||||
TgiApi->Update();
|
||||
|
||||
FDateTime Now = FDateTime::UtcNow();
|
||||
IStreamsProvider* StreamsProvider = nullptr;
|
||||
bool bIsEmulating = false;
|
||||
if (CVarEnableEyetrackingEmulation.GetValueOnGameThread())
|
||||
{
|
||||
bIsEmulating = true;
|
||||
GazeTrackerStatus = ETobiiGazeTrackerStatus::UserPresent;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Test for status
|
||||
StreamsProvider = TgiApi->GetStreamsProvider();
|
||||
if (StreamsProvider != nullptr && TrackerController != nullptr && TrackerController->IsConnected())
|
||||
{
|
||||
if (GazeTrackerStatus < ETobiiGazeTrackerStatus::UserNotPresent)
|
||||
{
|
||||
GazeTrackerStatus = ETobiiGazeTrackerStatus::UserNotPresent;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GazeTrackerStatus = ETobiiGazeTrackerStatus::NotConnected;
|
||||
}
|
||||
}
|
||||
|
||||
if (GazeTrackerStatus >= ETobiiGazeTrackerStatus::UserNotPresent)
|
||||
{
|
||||
if (bIsEmulating)
|
||||
{
|
||||
RawGazePoint.TimeStamp = Now;
|
||||
RawGazePoint.GazePointNormalized = TickEmulatedGazePointUNorm(DeltaTime);
|
||||
HeadPoseData.HeadLocation = FVector::ZeroVector;
|
||||
HeadPoseData.HeadOrientation = FRotator::ZeroRotator;
|
||||
InfiniteScreenAngles = FRotator::ZeroRotator;
|
||||
}
|
||||
else
|
||||
{
|
||||
GazeTrackerStatus = StreamsProvider->IsPresent() ? ETobiiGazeTrackerStatus::UserPresent : ETobiiGazeTrackerStatus::UserNotPresent;
|
||||
|
||||
//Get new gaze data
|
||||
const GazePoint* GazePointsSinceLastUpdateSNorm;
|
||||
int NumGazePointsSinceLastUpdate = StreamsProvider->GetGazePoints(GazePointsSinceLastUpdateSNorm);
|
||||
if (NumGazePointsSinceLastUpdate > 0)
|
||||
{
|
||||
RawGazePoint.GazePointNormalized.Set(0.0f, 0.0f);
|
||||
for (int32 GazeIdx = 0; GazeIdx < NumGazePointsSinceLastUpdate; GazeIdx++)
|
||||
{
|
||||
RawGazePoint.GazePointNormalized += FVector2D(GazePointsSinceLastUpdateSNorm[GazeIdx].X, GazePointsSinceLastUpdateSNorm[GazeIdx].Y);
|
||||
}
|
||||
|
||||
RawGazePoint.TimeStamp = Now;
|
||||
RawGazePoint.GazePointNormalized /= (float)NumGazePointsSinceLastUpdate;
|
||||
|
||||
//Convert to UNorm
|
||||
RawGazePoint.GazePointNormalized.Set((RawGazePoint.GazePointNormalized.X + 1.0f) / 2.0f
|
||||
, (-RawGazePoint.GazePointNormalized.Y + 1.0f) / 2.0f);
|
||||
}
|
||||
|
||||
//Get new head pose data
|
||||
const HeadPose* HeadPosesSinceLastUpdate;
|
||||
int NumHeadPosesSinceLastUpdate = StreamsProvider->GetHeadPoses(HeadPosesSinceLastUpdate);
|
||||
if (NumHeadPosesSinceLastUpdate > 0)
|
||||
{
|
||||
RawHeadPose.HeadPositionCm.Set(0.0f, 0.0f, 0.0f);
|
||||
float RawPitchRad = 0.0f;
|
||||
float RawYawRad = 0.0f;
|
||||
float RawRollRad = 0.0f;
|
||||
|
||||
for (int32 HeadPoseIdx = 0; HeadPoseIdx < NumHeadPosesSinceLastUpdate; HeadPoseIdx++)
|
||||
{
|
||||
RawHeadPose.HeadPositionCm += FVector(HeadPosesSinceLastUpdate[HeadPoseIdx].Position.X, HeadPosesSinceLastUpdate[HeadPoseIdx].Position.Y, HeadPosesSinceLastUpdate[HeadPoseIdx].Position.Z);
|
||||
RawPitchRad += HeadPosesSinceLastUpdate[HeadPoseIdx].Rotation.Pitch;
|
||||
RawYawRad += HeadPosesSinceLastUpdate[HeadPoseIdx].Rotation.Yaw;
|
||||
RawRollRad += HeadPosesSinceLastUpdate[HeadPoseIdx].Rotation.Roll;
|
||||
}
|
||||
|
||||
RawHeadPose.HeadPositionCm /= (float)NumHeadPosesSinceLastUpdate;
|
||||
RawPitchRad /= (float)NumHeadPosesSinceLastUpdate;
|
||||
RawYawRad /= (float)NumHeadPosesSinceLastUpdate;
|
||||
RawRollRad /= (float)NumHeadPosesSinceLastUpdate;
|
||||
|
||||
RawHeadPose.TimeStamp = Now;
|
||||
RawHeadPose.HeadPositionCm /= 10.0f; //Unit conversion
|
||||
RawHeadPose.HeadOrientation = FRotator(FMath::RadiansToDegrees(RawPitchRad), FMath::RadiansToDegrees(RawYawRad), FMath::RadiansToDegrees(RawRollRad));
|
||||
}
|
||||
|
||||
//Calculate derived data
|
||||
const float HeadPosePositionInterpolationBias = FMath::Clamp(CVarTobiiHeadPosePositionInterpolationBias.GetValueOnGameThread(), 0.3f, 1.0f);
|
||||
const float HeadPoseRotationInterpolationBias = FMath::Clamp(CVarTobiiHeadPoseRotationInterpolationBias.GetValueOnGameThread(), 0.01f, 1.0f);
|
||||
|
||||
HeadPoseData.HeadLocation = FMath::LerpStable(HeadPoseData.HeadLocation, RawHeadPose.HeadPositionCm, HeadPosePositionInterpolationBias);
|
||||
HeadPoseData.HeadOrientation = FQuat::Slerp(HeadPoseData.HeadOrientation.Quaternion(), RawHeadPose.HeadOrientation.Quaternion(), HeadPoseRotationInterpolationBias).Rotator();
|
||||
|
||||
//Infinite screen
|
||||
bool bExtendedViewUpdateSuccessful = false;
|
||||
IFeatures* Features = TgiApi->GetFeatures();
|
||||
if (CVarInfiniteScreenEnabled.GetValueOnGameThread() && !bIsXR && Features != nullptr)
|
||||
{
|
||||
IExtendedView* ExtendedView = Features->GetExtendedView();
|
||||
if (ExtendedView != nullptr)
|
||||
{
|
||||
const float BaseHeadViewResponsiveness = FMath::Clamp(CVarExtendedViewHeadSensitivity.GetValueOnGameThread(), 0.0f, 1.0f);
|
||||
const float BaseGazeViewResponsiveness = FMath::Clamp(CVarExtendedViewGazeSensitivity.GetValueOnGameThread(), 0.0f, 1.0f);
|
||||
|
||||
ExtendedViewSettings Settings;
|
||||
Settings.NormalizedGazeViewMinimumExtensionAngle = Settings.NormalizedGazeViewExtensionAngle = CVarExtendedViewMaxGazeAngleDeg.GetValueOnGameThread() / 360.0f;
|
||||
Settings.HeadViewResponsiveness = BaseHeadViewResponsiveness;
|
||||
Settings.GazeViewResponsiveness = BaseGazeViewResponsiveness;
|
||||
Settings.HeadViewAutoCenter.IsEnabled = false;
|
||||
|
||||
//Update default settings
|
||||
ExtendedView->UpdateSettings(Settings);
|
||||
|
||||
//Update specific settings
|
||||
Settings.GazeViewResponsiveness = BaseGazeViewResponsiveness * CVarExtendedViewGazeOnlyScalar.GetValueOnGameThread();
|
||||
ExtendedView->UpdateGazeOnlySettings(Settings);
|
||||
Settings.HeadViewResponsiveness = BaseHeadViewResponsiveness * CVarExtendedViewHeadOnlyScalar.GetValueOnGameThread();
|
||||
ExtendedView->UpdateHeadOnlySettings(Settings);
|
||||
|
||||
Transformation ExtendedViewData = ExtendedView->GetTransformation();
|
||||
InfiniteScreenAngles.Roll = 0.0f;
|
||||
InfiniteScreenAngles.Yaw = ExtendedViewData.Rotation.Yaw;
|
||||
InfiniteScreenAngles.Pitch = ExtendedViewData.Rotation.Pitch;
|
||||
bExtendedViewUpdateSuccessful = true;
|
||||
}
|
||||
}
|
||||
if (!bExtendedViewUpdateSuccessful)
|
||||
{
|
||||
InfiniteScreenAngles = FRotator::ZeroRotator;
|
||||
}
|
||||
}
|
||||
|
||||
//Since these are not time based, only run when necessary.
|
||||
static FDateTime PreviousRawGazePointTime;
|
||||
if (PreviousRawGazePointTime != RawGazePoint.TimeStamp)
|
||||
{
|
||||
GazePointDeltaTimeMicroSecs = (uint64)FMath::Max((RawGazePoint.TimeStamp - PreviousRawGazePointTime).GetTotalMicroseconds(), 0.0);
|
||||
CombinedGazeData.TimeStamp = RawGazePoint.TimeStamp;
|
||||
FVector2D ScreenSpaceGazePointUNorm = ConvertRawGazePointUNormToGameViewportCoordinateUNorm(GEngine->GameViewport->GetGameViewport(), RawGazePoint.GazePointNormalized);
|
||||
CombinedGazeData.ScreenGazePointPx.Set(ScreenSpaceGazePointUNorm.X * DisplayInfo.MainViewportWidthPx, ScreenSpaceGazePointUNorm.Y * DisplayInfo.MainViewportHeightPx);
|
||||
|
||||
PreviousRawGazePointTime = RawGazePoint.TimeStamp;
|
||||
}
|
||||
GazeDataTimeStampMicroSecs = (PreviousRawGazePointTime - StartTime).GetTotalMicroseconds();
|
||||
|
||||
const float AspectRatio = DisplayInfo.MainViewportWidthPx / (float)DisplayInfo.MainViewportHeightPx;
|
||||
const float FovealRegionSizeDeg = FMath::Max(CVarTobiiFovealConeAngleDegrees.GetValueOnGameThread(), 0.0f);
|
||||
CombinedGazeData.ScreenGazeCircleRadiiPx.Y = FEyetrackingUtils::CalculateFovealRegionHeightPx(DisplayInfo.MainViewportHeightCm, DisplayInfo.MainViewportHeightPx, HeadPoseData.HeadLocation.Z, FovealRegionSizeDeg);
|
||||
CombinedGazeData.ScreenGazeCircleRadiiPx.X = CombinedGazeData.ScreenGazeCircleRadiiPx.Y * AspectRatio;
|
||||
CombinedGazeData.WorldGazeConeAngleDegrees = FovealRegionSizeDeg;
|
||||
CombinedGazeData.EyeOpenness = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void FTobiiEyeTracker::TickXR(float DeltaTime)
|
||||
{
|
||||
//Pump API
|
||||
ITrackerController* TrackerController = TgiApi->GetTrackerController();
|
||||
if (TrackerController != nullptr)
|
||||
{
|
||||
TrackerController->TrackHMD();
|
||||
}
|
||||
TgiApi->Update();
|
||||
|
||||
//Test for status
|
||||
FDateTime Now = FDateTime::UtcNow();
|
||||
IStreamsProvider* StreamsProvider = nullptr;
|
||||
bool bIsEmulating = false;
|
||||
if (CVarEnableEyetrackingEmulation.GetValueOnGameThread())
|
||||
{
|
||||
bIsEmulating = true;
|
||||
GazeTrackerStatus = ETobiiGazeTrackerStatus::UserPresent;
|
||||
}
|
||||
else
|
||||
{
|
||||
StreamsProvider = TgiApi->GetStreamsProvider();
|
||||
GazeTrackerStatus = StreamsProvider != nullptr && TrackerController != nullptr && TrackerController->IsConnected()
|
||||
? ETobiiGazeTrackerStatus::UserPresent
|
||||
: ETobiiGazeTrackerStatus::NotConnected;
|
||||
}
|
||||
|
||||
if (GazeTrackerStatus == ETobiiGazeTrackerStatus::UserPresent && ActivePlayerController.IsValid())
|
||||
{
|
||||
bool bNewGazeData = false;
|
||||
|
||||
if (bIsEmulating)
|
||||
{
|
||||
FQuat HMDOrientation;
|
||||
FVector HMDPosition;
|
||||
GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, HMDOrientation, HMDPosition);
|
||||
|
||||
FVector2D& GazeModifierUNorm = TickEmulatedGazePointUNorm(DeltaTime);
|
||||
FVector2D GazeModifierSNorm((GazeModifierUNorm.X - 0.5f) * 2.0f, (GazeModifierUNorm.Y - 0.5f) * 2.0f);
|
||||
HMDOrientation *= FQuat::MakeFromEuler(FVector(0.0f, -GazeModifierSNorm.Y * 90.0f, GazeModifierSNorm.X * 90.0f));
|
||||
LeftGazeData.WorldGazeDirection = RightGazeData.WorldGazeDirection = HMDOrientation.GetForwardVector();
|
||||
|
||||
LeftGazeData.bIsGazeDataValid = RightGazeData.bIsGazeDataValid = true;
|
||||
LeftGazeData.EyeOpenness = RightGazeData.EyeOpenness = 1.0f;
|
||||
bNewGazeData = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Find direction and openness
|
||||
//const HMDGaze* RawTobiiGazeData;
|
||||
HMDGaze RawTobiiGazeData;
|
||||
//int NumDataSinceLastUpdate = StreamsProvider->GetHMDGaze(RawTobiiGazeData);
|
||||
int NumDataSinceLastUpdate = StreamsProvider->GetLatestHMDGaze(RawTobiiGazeData) ? 1 : 0;
|
||||
if (NumDataSinceLastUpdate > 0)
|
||||
{
|
||||
const HMDGaze& LatestGazeData = RawTobiiGazeData;//[NumDataSinceLastUpdate - 1];
|
||||
LeftGazeData.bIsGazeDataValid = (LatestGazeData.Validity & HMDValidityFlags::LeftEyeIsValid) == HMDValidityFlags::LeftEyeIsValid;
|
||||
RightGazeData.bIsGazeDataValid = (LatestGazeData.Validity & HMDValidityFlags::RightEyeIsValid) == HMDValidityFlags::RightEyeIsValid;
|
||||
|
||||
LeftGazeData.WorldGazeDirection.Set(0.0f, 0.0f, 0.0f);
|
||||
RightGazeData.WorldGazeDirection.Set(0.0f, 0.0f, 0.0f);
|
||||
for (int32 GazeIdx = 0; GazeIdx < NumDataSinceLastUpdate; GazeIdx++)
|
||||
{
|
||||
const HMDGaze& GazeData = RawTobiiGazeData;// [GazeIdx];
|
||||
LeftGazeData.WorldGazeDirection += FVector(GazeData.LeftEyeInfo.GazeDirection.Z, -GazeData.LeftEyeInfo.GazeDirection.X, GazeData.LeftEyeInfo.GazeDirection.Y);
|
||||
RightGazeData.WorldGazeDirection += FVector(GazeData.RightEyeInfo.GazeDirection.Z, -GazeData.RightEyeInfo.GazeDirection.X, GazeData.RightEyeInfo.GazeDirection.Y);
|
||||
}
|
||||
LeftGazeData.bIsGazeDataValid = LeftGazeData.bIsGazeDataValid && LeftGazeData.WorldGazeDirection.Normalize();
|
||||
RightGazeData.bIsGazeDataValid = RightGazeData.bIsGazeDataValid && RightGazeData.WorldGazeDirection.Normalize();
|
||||
|
||||
LeftGazeData.EyeOpenness = LatestGazeData.LeftEyeInfo.EyeOpenness;
|
||||
RightGazeData.EyeOpenness = LatestGazeData.RightEyeInfo.EyeOpenness;
|
||||
GazePointDeltaTimeMicroSecs = (int64)LatestGazeData.Timestamp - GazeDataTimeStampMicroSecs;
|
||||
GazeDataTimeStampMicroSecs = (int64)LatestGazeData.Timestamp;
|
||||
bNewGazeData = true;
|
||||
}
|
||||
|
||||
//Optionally correct orientation depending on project settings
|
||||
{
|
||||
if (CVarTobiiApplyHMDOrientation.GetValueOnGameThread())
|
||||
{
|
||||
FQuat HMDOrientation;
|
||||
FVector HMDPosition;
|
||||
GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, HMDOrientation, HMDPosition);
|
||||
LeftGazeData.WorldGazeDirection = HMDOrientation.RotateVector(LeftGazeData.WorldGazeDirection);
|
||||
RightGazeData.WorldGazeDirection = HMDOrientation.RotateVector(RightGazeData.WorldGazeDirection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CVarTobiiApplyActorRotation.GetValueOnGameThread()
|
||||
&& ActivePlayerController.IsValid()
|
||||
&& ActivePlayerController->GetPawn() != nullptr)
|
||||
{
|
||||
FQuat ActorRotation = ActivePlayerController->GetPawn()->GetActorRotation().Quaternion();
|
||||
|
||||
LeftGazeData.WorldGazeDirection = ActorRotation.RotateVector(LeftGazeData.WorldGazeDirection);
|
||||
RightGazeData.WorldGazeDirection = ActorRotation.RotateVector(RightGazeData.WorldGazeDirection);
|
||||
}
|
||||
|
||||
//Calculate combined data
|
||||
CombinedGazeData.bIsGazeDataValid = LeftGazeData.bIsGazeDataValid || RightGazeData.bIsGazeDataValid;
|
||||
if (LeftGazeData.bIsGazeDataValid && RightGazeData.bIsGazeDataValid)
|
||||
{
|
||||
CombinedGazeData.WorldGazeDirection = (LeftGazeData.WorldGazeDirection + RightGazeData.WorldGazeDirection);
|
||||
CombinedGazeData.bIsGazeDataValid = CombinedGazeData.bIsGazeDataValid && CombinedGazeData.WorldGazeDirection.Normalize();
|
||||
}
|
||||
else if (LeftGazeData.bIsGazeDataValid)
|
||||
{
|
||||
CombinedGazeData.WorldGazeDirection = LeftGazeData.WorldGazeDirection;
|
||||
}
|
||||
else if (RightGazeData.bIsGazeDataValid)
|
||||
{
|
||||
CombinedGazeData.WorldGazeDirection = RightGazeData.WorldGazeDirection;
|
||||
}
|
||||
|
||||
if (bNewGazeData)
|
||||
{
|
||||
CombinedGazeData.TimeStamp = Now;
|
||||
}
|
||||
|
||||
const float AspectRatio = DisplayInfo.MainViewportWidthPx / (float)DisplayInfo.MainViewportHeightPx;
|
||||
const float FovealRegionSizeDeg = FMath::Max(CVarTobiiFovealConeAngleDegrees.GetValueOnGameThread(), 0.0f);
|
||||
CombinedGazeData.ScreenGazeCircleRadiiPx.Y = FEyetrackingUtils::CalculateFovealRegionHeightPx(DisplayInfo.MainViewportHeightCm, DisplayInfo.MainViewportHeightPx, CVarHMDScreenDistanceToEyeCm.GetValueOnGameThread(), FovealRegionSizeDeg);
|
||||
CombinedGazeData.ScreenGazeCircleRadiiPx.X = CombinedGazeData.ScreenGazeCircleRadiiPx.Y * AspectRatio;
|
||||
LeftGazeData.WorldGazeConeAngleDegrees = RightGazeData.WorldGazeConeAngleDegrees = CombinedGazeData.WorldGazeConeAngleDegrees = FovealRegionSizeDeg;
|
||||
LeftGazeData.ScreenGazeCircleRadiiPx = RightGazeData.ScreenGazeCircleRadiiPx = CombinedGazeData.ScreenGazeCircleRadiiPx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FTobiiEyeTracker::UpdateWorldSpaceData(float DeltaTime)
|
||||
{
|
||||
if (ActivePlayerController.IsValid() && ActivePlayerController->GetWorld() != nullptr)
|
||||
{
|
||||
if (bIsXR)
|
||||
{
|
||||
ULocalPlayer* const LocalPlayer = ActivePlayerController->GetLocalPlayer();
|
||||
if (LocalPlayer != nullptr)
|
||||
{
|
||||
//Find origin and screen point. This also calculates screen data for VR as it is expressed in world space data.
|
||||
//@TODO: We need to make it obvious that if you want to line trace here, you must move the origin forward to the near plane.
|
||||
const float ProjectionDistanceCm = 1000.0f;
|
||||
FSceneViewProjectionData LeftProjectionData, RightProjectionData;
|
||||
if (LocalPlayer->GetProjectionData(LocalPlayer->ViewportClient->Viewport, eSSP_LEFT_EYE, /*out*/ LeftProjectionData))
|
||||
{
|
||||
LeftGazeData.WorldGazeOrigin = LeftProjectionData.ViewOrigin;
|
||||
const FVector ProjectionPoint = LeftGazeData.WorldGazeOrigin + (LeftGazeData.WorldGazeDirection * ProjectionDistanceCm);
|
||||
ActivePlayerController->ProjectWorldLocationToScreen(ProjectionPoint, LeftGazeData.ScreenGazePointPx);
|
||||
}
|
||||
else if (ActivePlayerController->PlayerCameraManager != nullptr)
|
||||
{
|
||||
LeftGazeData.WorldGazeOrigin = ActivePlayerController->PlayerCameraManager->GetCameraLocation();
|
||||
}
|
||||
if (LocalPlayer->GetProjectionData(LocalPlayer->ViewportClient->Viewport, eSSP_RIGHT_EYE, /*out*/ RightProjectionData))
|
||||
{
|
||||
RightGazeData.WorldGazeOrigin = RightProjectionData.ViewOrigin;
|
||||
const FVector ProjectionPoint = RightGazeData.WorldGazeOrigin + (RightGazeData.WorldGazeDirection * ProjectionDistanceCm);
|
||||
ActivePlayerController->ProjectWorldLocationToScreen(ProjectionPoint, RightGazeData.ScreenGazePointPx);
|
||||
}
|
||||
else if (ActivePlayerController->PlayerCameraManager != nullptr)
|
||||
{
|
||||
RightGazeData.WorldGazeOrigin = ActivePlayerController->PlayerCameraManager->GetCameraLocation();
|
||||
}
|
||||
|
||||
CombinedGazeData.WorldGazeOrigin = (LeftGazeData.WorldGazeOrigin + RightGazeData.WorldGazeOrigin) / 2.0f;
|
||||
CombinedGazeData.ScreenGazePointPx = (LeftGazeData.ScreenGazePointPx + RightGazeData.ScreenGazePointPx) / 2.0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CombinedGazeData.bIsGazeDataValid = ActivePlayerController->DeprojectScreenPositionToWorld(CombinedGazeData.ScreenGazePointPx.X, CombinedGazeData.ScreenGazePointPx.Y, CombinedGazeData.WorldGazeOrigin, CombinedGazeData.WorldGazeDirection);
|
||||
CombinedGazeData.WorldGazeOrigin = ActivePlayerController->PlayerCameraManager->GetCameraLocation();
|
||||
LeftGazeData = RightGazeData = CombinedGazeData;
|
||||
}
|
||||
|
||||
//Verify direction validity
|
||||
if (!CombinedGazeData.WorldGazeDirection.IsNormalized())
|
||||
{
|
||||
CombinedGazeData.bIsGazeDataValid = false;
|
||||
}
|
||||
if (!LeftGazeData.WorldGazeDirection.IsNormalized())
|
||||
{
|
||||
LeftGazeData.bIsGazeDataValid = false;
|
||||
}
|
||||
if (!RightGazeData.WorldGazeDirection.IsNormalized())
|
||||
{
|
||||
RightGazeData.bIsGazeDataValid = false;
|
||||
}
|
||||
|
||||
FCollisionQueryParams CollisionQueryParams;
|
||||
CollisionQueryParams.AddIgnoredActor(ActivePlayerController.Get());
|
||||
CollisionQueryParams.AddIgnoredActor(ActivePlayerController->GetPawn());
|
||||
const float MaximumTraceDistance = FMath::Max(CVarTobiiMaximumTraceDistance.GetValueOnGameThread(), 0.0f);
|
||||
const FVector CombinedGazeFarLocation = CombinedGazeData.WorldGazeOrigin + (CombinedGazeData.WorldGazeDirection * MaximumTraceDistance);
|
||||
if (!CombinedGazeData.bIsGazeDataValid ||
|
||||
!ActivePlayerController->GetWorld()->LineTraceSingleByChannel(CombinedWorldGazeHitData, CombinedGazeData.WorldGazeOrigin
|
||||
, CombinedGazeFarLocation, (ECollisionChannel)CVarTobiiFocusTraceChannel.GetValueOnGameThread(), CollisionQueryParams))
|
||||
{
|
||||
CombinedWorldGazeHitData.Actor = nullptr;
|
||||
CombinedWorldGazeHitData.Component = nullptr;
|
||||
CombinedWorldGazeHitData.Distance = MaximumTraceDistance;
|
||||
CombinedWorldGazeHitData.Location = CombinedGazeFarLocation;
|
||||
CombinedWorldGazeHitData.bBlockingHit = false;
|
||||
}
|
||||
|
||||
const FVector LeftGazeFarLocation = LeftGazeData.WorldGazeOrigin + (LeftGazeData.WorldGazeDirection * MaximumTraceDistance);
|
||||
if (!LeftGazeData.bIsGazeDataValid ||
|
||||
!ActivePlayerController->GetWorld()->LineTraceSingleByChannel(LeftWorldGazeHitData, LeftGazeData.WorldGazeOrigin
|
||||
, LeftGazeFarLocation, (ECollisionChannel)CVarTobiiFocusTraceChannel.GetValueOnGameThread(), CollisionQueryParams))
|
||||
{
|
||||
LeftWorldGazeHitData.Actor = nullptr;
|
||||
LeftWorldGazeHitData.Component = nullptr;
|
||||
LeftWorldGazeHitData.Distance = MaximumTraceDistance;
|
||||
LeftWorldGazeHitData.Location = CombinedGazeFarLocation;
|
||||
LeftWorldGazeHitData.bBlockingHit = false;
|
||||
}
|
||||
|
||||
const FVector RightGazeFarLocation = RightGazeData.WorldGazeOrigin + (RightGazeData.WorldGazeDirection * MaximumTraceDistance);
|
||||
if (!RightGazeData.bIsGazeDataValid ||
|
||||
!ActivePlayerController->GetWorld()->LineTraceSingleByChannel(RightWorldGazeHitData, RightGazeData.WorldGazeOrigin
|
||||
, RightGazeFarLocation, (ECollisionChannel)CVarTobiiFocusTraceChannel.GetValueOnGameThread(), CollisionQueryParams))
|
||||
{
|
||||
RightWorldGazeHitData.Actor = nullptr;
|
||||
RightWorldGazeHitData.Component = nullptr;
|
||||
RightWorldGazeHitData.Distance = MaximumTraceDistance;
|
||||
RightWorldGazeHitData.Location = CombinedGazeFarLocation;
|
||||
RightWorldGazeHitData.bBlockingHit = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FTobiiEyeTracker::UpdateStabilityData(float DeltaTime)
|
||||
{
|
||||
if (CVarEnableEyetrackingEmulation.GetValueOnGameThread() && bIsXR)
|
||||
{
|
||||
CombinedGazeData.bIsStable = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//We will calculate the average gaze point velocity using a very simple moving cumulative average.
|
||||
//If the average velocity of these points is under some predetermined threshold, we will consider it stable.
|
||||
if (GazePointDeltaTimeMicroSecs > 0)
|
||||
{
|
||||
const float GazeAngleDiffDeg = FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(CombinedGazeData.WorldGazeDirection, PrevCombinedGazeDirection)));
|
||||
const double AngularSpeedDegPerMicroSecs = GazeAngleDiffDeg / (double)GazePointDeltaTimeMicroSecs;
|
||||
const float StablePointInterpolationBias = FMath::Clamp(CVarTobiiStablePointInterpolationBias.GetValueOnGameThread(), 0.3f, 1.0f);
|
||||
const float NewAvgSpeed = FMath::LerpStable(CurrentAverageGazeAngularSpeedDegPerMicroSecs, AngularSpeedDegPerMicroSecs, StablePointInterpolationBias);
|
||||
CurrentAverageGazeAngularSpeedDegPerMicroSecs = FMath::IsFinite(NewAvgSpeed) ? NewAvgSpeed : 0.0f; //Protect from overflow
|
||||
|
||||
PrevCombinedGazeDirection = CombinedGazeData.WorldGazeDirection;
|
||||
|
||||
const float MaxAcceptableStablePointAverageSpeed = FMath::Max(CVarTobiiMaximumAcceptableStableGazeAverageAngularSpeedDegPerMicroSecs.GetValueOnGameThread(), 0.0f);
|
||||
CombinedGazeData.bIsStable = CurrentAverageGazeAngularSpeedDegPerMicroSecs <= MaxAcceptableStablePointAverageSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
LeftGazeData.bIsStable = RightGazeData.bIsStable = CombinedGazeData.bIsStable;
|
||||
}
|
||||
|
||||
void FTobiiEyeTracker::SetEyeTrackedPlayer(APlayerController* PlayerController)
|
||||
{
|
||||
if (PlayerController != nullptr)
|
||||
{
|
||||
ActivePlayerController = PlayerController;
|
||||
}
|
||||
else if (GEngine != nullptr && GEngine->GameViewport != nullptr)
|
||||
{
|
||||
ActivePlayerController = GEngine->GetFirstLocalPlayerController(GEngine->GameViewport->GetWorld());
|
||||
}
|
||||
|
||||
bIsXR = UTobiiBlueprintLibrary::IsXRPlayerController(ActivePlayerController.Get());
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
/* IGazeTracker */
|
||||
/************************************************************************/
|
||||
const FTobiiGazeData& FTobiiEyeTracker::GetCombinedGazeData() const
|
||||
{
|
||||
return CombinedGazeData;
|
||||
}
|
||||
|
||||
const FTobiiGazeData& FTobiiEyeTracker::GetLeftGazeData() const
|
||||
{
|
||||
return LeftGazeData;
|
||||
}
|
||||
|
||||
const FTobiiGazeData& FTobiiEyeTracker::GetRightGazeData() const
|
||||
{
|
||||
return RightGazeData;
|
||||
}
|
||||
|
||||
ETobiiGazeTrackerStatus FTobiiEyeTracker::GetGazeTrackerStatus() const
|
||||
{
|
||||
return GazeTrackerStatus;
|
||||
}
|
||||
|
||||
bool FTobiiEyeTracker::GetGazeTrackerCapability(ETobiiGazeTrackerCapability Capability) const
|
||||
{
|
||||
//Desktop trackers only support CombinedGazeData
|
||||
return bIsXR || Capability == ETobiiGazeTrackerCapability::CombinedGazeData;
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
/* IHeadTracker */
|
||||
/************************************************************************/
|
||||
const FTobiiHeadPoseData& FTobiiEyeTracker::GetHeadPoseData() const
|
||||
{
|
||||
return HeadPoseData;
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
/* ITobiiEyetracker */
|
||||
/************************************************************************/
|
||||
const FHitResult& FTobiiEyeTracker::GetCombinedWorldGazeHitData() const
|
||||
{
|
||||
return CombinedWorldGazeHitData;
|
||||
}
|
||||
|
||||
const FHitResult& FTobiiEyeTracker::GetLeftWorldGazeHitData() const
|
||||
{
|
||||
return LeftWorldGazeHitData;
|
||||
}
|
||||
|
||||
const FHitResult& FTobiiEyeTracker::GetRightWorldGazeHitData() const
|
||||
{
|
||||
return RightWorldGazeHitData;
|
||||
}
|
||||
|
||||
const FTobiiDisplayInfo& FTobiiEyeTracker::GetDisplayInformation() const
|
||||
{
|
||||
return DisplayInfo;
|
||||
}
|
||||
|
||||
const FTobiiDesktopTrackBox& FTobiiEyeTracker::GetDesktopTrackBox() const
|
||||
{
|
||||
return DesktopTrackBox;
|
||||
}
|
||||
|
||||
const FRotator& FTobiiEyeTracker::GetInfiniteScreenAngles() const
|
||||
{
|
||||
return InfiniteScreenAngles;
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
/* IEyetracker */
|
||||
/************************************************************************/
|
||||
bool FTobiiEyeTracker::GetEyeTrackerGazeData(FEyeTrackerGazeData& OutGazeData) const
|
||||
{
|
||||
OutGazeData.ConfidenceValue = CombinedGazeData.bIsGazeDataValid ? 1.0f : 0.0f;
|
||||
OutGazeData.FixationPoint = FVector::ZeroVector; //This data is not useful for a game designer as it is not reliable enough, so we don't supply it.
|
||||
OutGazeData.GazeOrigin = CombinedGazeData.WorldGazeOrigin;
|
||||
OutGazeData.GazeDirection = CombinedGazeData.WorldGazeDirection;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FTobiiEyeTracker::GetEyeTrackerStereoGazeData(FEyeTrackerStereoGazeData& OutGazeData) const
|
||||
{
|
||||
OutGazeData.ConfidenceValue = CombinedGazeData.bIsGazeDataValid ? 1.0f : 0.0f;
|
||||
OutGazeData.FixationPoint = FVector::ZeroVector; //This data is not useful for a game designer as it is not reliable enough, so we don't supply it.
|
||||
OutGazeData.LeftEyeOrigin = LeftGazeData.WorldGazeOrigin;
|
||||
OutGazeData.LeftEyeDirection = LeftGazeData.WorldGazeDirection;
|
||||
OutGazeData.RightEyeOrigin = RightGazeData.WorldGazeOrigin;
|
||||
OutGazeData.RightEyeOrigin = RightGazeData.WorldGazeDirection;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
EEyeTrackerStatus FTobiiEyeTracker::GetEyeTrackerStatus() const
|
||||
{
|
||||
switch (GazeTrackerStatus)
|
||||
{
|
||||
case ETobiiGazeTrackerStatus::NotConnected: return EEyeTrackerStatus::NotConnected;
|
||||
case ETobiiGazeTrackerStatus::NotConfigured: return EEyeTrackerStatus::NotConnected;
|
||||
case ETobiiGazeTrackerStatus::Disabled: return EEyeTrackerStatus::NotConnected;
|
||||
case ETobiiGazeTrackerStatus::UserNotPresent: return EEyeTrackerStatus::NotTracking;
|
||||
case ETobiiGazeTrackerStatus::UserPresent: return EEyeTrackerStatus::Tracking;
|
||||
default: return EEyeTrackerStatus::NotConnected;
|
||||
}
|
||||
}
|
||||
|
||||
FVector2D FTobiiEyeTracker::ConvertRawGazePointUNormToGameViewportCoordinateUNorm(FSceneViewport* GameViewport, const FVector2D& InNormalizedPoint)
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
//We need this to make sure we get a correct gaze point if the editor has tool windows attached to the window itself.
|
||||
FIntPoint VirtualDesktopPixelCoord;
|
||||
if (FTobiiPlatformSpecific::ConvertGazeCoordinateToVirtualDesktopPixel(DisplayInfo.GameWindowHandle, InNormalizedPoint, VirtualDesktopPixelCoord))
|
||||
{
|
||||
FVector2D ViewportPixelCoordUNorm;
|
||||
if (UTobiiBlueprintLibrary::VirtualDesktopPixelToViewportCoordinateUNorm(VirtualDesktopPixelCoord, ViewportPixelCoordUNorm))
|
||||
{
|
||||
return ViewportPixelCoordUNorm;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return InNormalizedPoint;
|
||||
}
|
||||
|
||||
// void FTobiiEyeTracker::DrawDebug(AHUD* HUD, UCanvas* Canvas, const FDebugDisplayInfo& DebugDisplayInfo, float& YL, float& YPos)
|
||||
// {
|
||||
// if (bIsXR)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// UWorld* World = Canvas->GetWorld();
|
||||
// UFont* RenderFont = GEngine->GetSmallFont();
|
||||
// EGazeTrackerStatus Status = GetGazeTrackerStatus();
|
||||
//
|
||||
// //First draw our status string
|
||||
// FString StatusText;
|
||||
// switch (Status)
|
||||
// {
|
||||
// case EGazeTrackerStatus::Disabled:
|
||||
// StatusText = "Eyetracking disabled";
|
||||
// Canvas->DrawColor = FColor::Magenta;
|
||||
// break;
|
||||
// case EGazeTrackerStatus::UserNotPresent:
|
||||
// StatusText = "User not present";
|
||||
// Canvas->DrawColor = FColor::Purple;
|
||||
// break;
|
||||
// case EGazeTrackerStatus::UserPresent:
|
||||
// StatusText = "User is present";
|
||||
// Canvas->DrawColor = FColor::Blue;
|
||||
// break;
|
||||
// default:
|
||||
// StatusText = "Eyetracking not available";
|
||||
// Canvas->DrawColor = FColor::Red;
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// TArray<FStringFormatArg> Args;
|
||||
// Args.Emplace(StatusText);
|
||||
//
|
||||
// //Can't use SizeX/Y since it wont work on other DPI settings
|
||||
// float CanvasDrawWidth = Canvas->ClipX - Canvas->OrgX;
|
||||
// float CanvasDrawHeight = Canvas->ClipY - Canvas->OrgY;
|
||||
//
|
||||
// Canvas->DrawText(RenderFont, FString::Format(TEXT("Status: {0}"), Args), 0.5f * CanvasDrawWidth - 100, 0.05f * CanvasDrawHeight);
|
||||
//
|
||||
// if (CVarEnableHeadPoseDebug.GetValueOnGameThread())
|
||||
// {
|
||||
// const float PoseDebugScale = 1.0f;
|
||||
//
|
||||
// FVector WorldPosition, WorldDirection;
|
||||
// HUD->Deproject(CanvasDrawWidth * 0.5f, CanvasDrawHeight * 0.8f, WorldPosition, WorldDirection);
|
||||
//
|
||||
// //First we figure our origin data
|
||||
// const FVector PoseDebugLocation = WorldPosition + WorldDirection * 200.0f;
|
||||
// const FQuat PoseDebugRotation = HUD->GetOwner()->GetActorRotation().Quaternion();
|
||||
//
|
||||
// FCanvasLineItem LineItem;
|
||||
// const FHeadPoseData& HeadPose = GetHeadPoseData();
|
||||
// const float BasisScale = PoseDebugScale * 7.0f;
|
||||
// LineItem.LineThickness = 3.0f;
|
||||
//
|
||||
// FQuat OriginRotation = PoseDebugRotation * HeadPose.HeadOrientation.Quaternion();
|
||||
// FVector ForwardBasis = OriginRotation.RotateVector(FVector::ForwardVector) * BasisScale;
|
||||
// FVector RightBasis = OriginRotation.RotateVector(FVector::RightVector) * BasisScale * 0.5f;
|
||||
// FVector UpBasis = OriginRotation.RotateVector(FVector::UpVector) * BasisScale * 0.5f;
|
||||
//
|
||||
// FVector OriginWorldPosition = PoseDebugLocation;// + PoseDebugRotation.RotateVector((HeadPose.HeadPositionCm - TrackBoxCenter) * PoseDebugScale);
|
||||
// FVector ProjectedOrigin = HUD->Project(OriginWorldPosition);
|
||||
// FVector ProjectedForward = HUD->Project(OriginWorldPosition + ForwardBasis);
|
||||
// FVector ProjectedRight = HUD->Project(OriginWorldPosition + RightBasis);
|
||||
// FVector ProjectedUp = HUD->Project(OriginWorldPosition + UpBasis);
|
||||
//
|
||||
// LineItem.SetColor(FLinearColor::Red);
|
||||
// LineItem.Origin = ProjectedOrigin;
|
||||
// LineItem.EndPos = ProjectedForward;
|
||||
// Canvas->DrawItem(LineItem);
|
||||
// LineItem.SetColor(FLinearColor::Green);
|
||||
// LineItem.Origin = ProjectedOrigin;
|
||||
// LineItem.EndPos = ProjectedRight;
|
||||
// Canvas->DrawItem(LineItem);
|
||||
// LineItem.SetColor(FLinearColor::Blue);
|
||||
// LineItem.Origin = ProjectedOrigin;
|
||||
// LineItem.EndPos = ProjectedUp;
|
||||
// Canvas->DrawItem(LineItem);
|
||||
// }
|
||||
// }
|
||||
|
||||
#endif //TOBII_EYETRACKING_ACTIVE
|
||||
104
Source/TobiiCore/Private/TobiiEyetracker.h
Normal file
104
Source/TobiiCore/Private/TobiiEyetracker.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2017- Tobii Technology AB. All rights reserved.
|
||||
*
|
||||
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if TOBII_EYETRACKING_ACTIVE
|
||||
|
||||
#include "ITobiiEyeTracker.h"
|
||||
#include "TobiiPlatformSpecific.h"
|
||||
#include "TobiiInternalTypes.h"
|
||||
#include "tobii_gameintegration.h"
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Containers/Ticker.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "Slate/SceneViewport.h"
|
||||
|
||||
/*
|
||||
* FTickerObjectBase here is ticked almost last in a frame.
|
||||
* The order seems to go 'IInputDevice', then shortly after that 'Actor/Components' and very late after that the 'TickerCore' and thereby also FTickerObjectBase.
|
||||
* No matter if we choose IInputDevice or FTickerObjectBase we will be working with complete physics data from the last frame.
|
||||
* Of course we would rather be working with current frame physics data, but doing that would make our data more TickGroup dependent.
|
||||
* Maybe that's fine, but seeing as our traces are not really precise anyways, I think the simplicity of design of using FTickerObjectBase wins out.
|
||||
*
|
||||
* The big alternative would be to move all GTOM related objects to an actor that belongs to the PostPhysics tick group together with the focus managers and introduce a tick dependency.
|
||||
* See FEngineLoop::Tick() for more detailed info.
|
||||
*/
|
||||
class FTobiiEyeTracker : public ITobiiEyeTracker, public FTickerObjectBase
|
||||
{
|
||||
public:
|
||||
FTobiiEyeTracker();
|
||||
virtual ~FTobiiEyeTracker();
|
||||
virtual bool Tick(float DeltaTime) override;
|
||||
void Shutdown();
|
||||
bool IsConnectedToEyeTracker();
|
||||
|
||||
/************************************************************************/
|
||||
/* ITobiiEyetracker */
|
||||
/************************************************************************/
|
||||
public:
|
||||
virtual const FTobiiGazeData& GetCombinedGazeData() const override;
|
||||
virtual const FTobiiGazeData& GetLeftGazeData() const override;
|
||||
virtual const FTobiiGazeData& GetRightGazeData() const override;
|
||||
virtual ETobiiGazeTrackerStatus GetGazeTrackerStatus() const override;
|
||||
virtual bool GetGazeTrackerCapability(ETobiiGazeTrackerCapability Capability) const override;
|
||||
|
||||
virtual const FHitResult& GetCombinedWorldGazeHitData() const override;
|
||||
virtual const FHitResult& GetLeftWorldGazeHitData() const override;
|
||||
virtual const FHitResult& GetRightWorldGazeHitData() const override;
|
||||
virtual const FTobiiDisplayInfo& GetDisplayInformation() const override;
|
||||
virtual const FTobiiHeadPoseData& GetHeadPoseData() const override;
|
||||
virtual const FTobiiDesktopTrackBox& GetDesktopTrackBox() const override;
|
||||
virtual const FRotator& GetInfiniteScreenAngles() const override;
|
||||
|
||||
public:
|
||||
virtual void SetEyeTrackedPlayer(APlayerController* PlayerController) override;
|
||||
virtual bool IsStereoGazeDataAvailable() const override { return bIsXR; }
|
||||
virtual bool GetEyeTrackerGazeData(FEyeTrackerGazeData& OutGazeData) const override;
|
||||
virtual bool GetEyeTrackerStereoGazeData(FEyeTrackerStereoGazeData& OutGazeData) const override;
|
||||
virtual EEyeTrackerStatus GetEyeTrackerStatus() const override;
|
||||
|
||||
private:
|
||||
TobiiGameIntegration::ITobiiGameIntegrationApi* TgiApi;
|
||||
FTobiiPlatformNotifications PlatformNotifications;
|
||||
TWeakObjectPtr<APlayerController> ActivePlayerController;
|
||||
|
||||
FHitResult CombinedWorldGazeHitData;
|
||||
FHitResult LeftWorldGazeHitData;
|
||||
FHitResult RightWorldGazeHitData;
|
||||
FTobiiGazeData LeftGazeData;
|
||||
FTobiiGazeData RightGazeData;
|
||||
FTobiiGazeData CombinedGazeData;
|
||||
ETobiiGazeTrackerStatus GazeTrackerStatus;
|
||||
FTobiiHeadPoseData HeadPoseData;
|
||||
FTobiiDisplayInfo DisplayInfo;
|
||||
FRotator InfiniteScreenAngles;
|
||||
FTobiiDesktopTrackBox DesktopTrackBox;
|
||||
|
||||
FTobiiRawGazePoint RawGazePoint;
|
||||
FTobiiRawHeadPose RawHeadPose;
|
||||
FVector PrevCombinedGazeDirection;
|
||||
|
||||
bool bIsXR;
|
||||
int64 GazeDataTimeStampMicroSecs;
|
||||
uint64 GazePointDeltaTimeMicroSecs;
|
||||
double CurrentAverageGazeAngularSpeedDegPerMicroSecs;
|
||||
FDateTime StartTime;
|
||||
|
||||
void ResetData();
|
||||
bool UpdateLowLevelResources();
|
||||
void TickDesktop(float DeltaTime);
|
||||
void TickXR(float DeltaTime);
|
||||
void UpdateWorldSpaceData(float DeltaTime);
|
||||
void UpdateStabilityData(float DeltaTime);
|
||||
|
||||
FVector2D ConvertRawGazePointUNormToGameViewportCoordinateUNorm(FSceneViewport* GameViewport, const FVector2D& InNormalizedPoint);
|
||||
|
||||
FVector2D& TickEmulatedGazePointUNorm(float DeltaTime);
|
||||
};
|
||||
|
||||
#endif //TOBII_EYETRACKING_ACTIVE
|
||||
76
Source/TobiiCore/Private/TobiiInternalTypes.h
Normal file
76
Source/TobiiCore/Private/TobiiInternalTypes.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2017- Tobii Technology AB. All rights reserved.
|
||||
*
|
||||
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "TobiiTypes.h"
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
class FEyetrackingUtils
|
||||
{
|
||||
public:
|
||||
static float CalculateFovealRegionHeightPx(const float MainViewportHeightCm, const float MainViewportHeightPx, const float HeadDistanceCm, const float FovealRegionAngleDeg)
|
||||
{
|
||||
if (MainViewportHeightCm > FLT_EPSILON && MainViewportHeightPx > FLT_EPSILON && HeadDistanceCm > FLT_EPSILON)
|
||||
{
|
||||
const float FovealRegionSizeCm = HeadDistanceCm * FMath::Tan(FMath::DegreesToRadians(FovealRegionAngleDeg));
|
||||
return FovealRegionSizeCm / MainViewportHeightCm * MainViewportHeightPx;
|
||||
}
|
||||
|
||||
return 100.0f;
|
||||
}
|
||||
};
|
||||
|
||||
struct FTobiiRawGazePoint
|
||||
{
|
||||
public:
|
||||
FVector2D GazePointNormalized;
|
||||
FDateTime TimeStamp;
|
||||
|
||||
FTobiiRawGazePoint()
|
||||
: GazePointNormalized(0.0f, 0.0f)
|
||||
, TimeStamp()
|
||||
{ }
|
||||
};
|
||||
|
||||
struct FTobiiRawHeadPose
|
||||
{
|
||||
public:
|
||||
FVector HeadPositionCm;
|
||||
FRotator HeadOrientation;
|
||||
FDateTime TimeStamp;
|
||||
|
||||
public:
|
||||
FTobiiRawHeadPose()
|
||||
: HeadPositionCm(0.0f, 0.0f, 0.0f)
|
||||
, HeadOrientation(0.0f, 0.0f, 0.0f)
|
||||
, TimeStamp()
|
||||
{}
|
||||
};
|
||||
|
||||
struct FTobiiRawXREyetrackingData
|
||||
{
|
||||
public:
|
||||
FVector RawLeftEyeDirection;
|
||||
FVector RawRightEyeDirection;
|
||||
FDateTime EngineTimeStamp;
|
||||
int64 EyetrackerTimeStamp;
|
||||
bool bLeftEyeValid;
|
||||
bool bRightEyeValid;
|
||||
|
||||
FTobiiRawXREyetrackingData()
|
||||
: RawLeftEyeDirection()
|
||||
, RawRightEyeDirection()
|
||||
, EngineTimeStamp()
|
||||
, EyetrackerTimeStamp(-1)
|
||||
, bLeftEyeValid(false)
|
||||
, bRightEyeValid(false)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogTobiiEyetracking, All, All);
|
||||
135
Source/TobiiCore/Private/TobiiPlatformSpecific.cpp
Normal file
135
Source/TobiiCore/Private/TobiiPlatformSpecific.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2017- Tobii Technology AB. All rights reserved.
|
||||
*
|
||||
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
|
||||
******************************************************************************/
|
||||
|
||||
#include "TobiiPlatformSpecific.h"
|
||||
|
||||
#include "Framework/Application/SlateApplication.h"
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
#include "Windows/AllowWindowsPlatformTypes.h"
|
||||
#include "dbt.h"
|
||||
|
||||
struct MonitorHandleContext
|
||||
{
|
||||
FString DeviceName;
|
||||
HMONITOR hMonitorHandle;
|
||||
};
|
||||
|
||||
BOOL CALLBACK EnumDisplayMonitorsProc(HMONITOR hMonitorHandle, HDC, LPRECT, LPARAM Data)
|
||||
{
|
||||
MonitorHandleContext* Context = (MonitorHandleContext*)Data;
|
||||
MONITORINFOEXA MonitorInfo;
|
||||
MonitorInfo.cbSize = sizeof(MonitorInfo);
|
||||
int res = GetMonitorInfoA(hMonitorHandle, &MonitorInfo);
|
||||
if (res && _stricmp(MonitorInfo.szDevice, TCHAR_TO_ANSI(*Context->DeviceName)) == 0)
|
||||
{
|
||||
Context->hMonitorHandle = hMonitorHandle;
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void* FTobiiPlatformSpecific::GetMonitorInformation(const void* GameWindowHandle, int& MonitorWidthPx, int& MonitorHeightPx)
|
||||
{
|
||||
HWND hGameWindowHandle = (HWND)GameWindowHandle;
|
||||
HMONITOR hGameMonitor = MonitorFromWindow(hGameWindowHandle, MONITOR_DEFAULTTONEAREST);
|
||||
MONITORINFO MonitorInfo;
|
||||
MonitorInfo.cbSize = sizeof(MONITORINFO);
|
||||
if (GetMonitorInfo(hGameMonitor, &MonitorInfo))
|
||||
{
|
||||
MonitorWidthPx = MonitorInfo.rcMonitor.right - MonitorInfo.rcMonitor.left;
|
||||
MonitorHeightPx = MonitorInfo.rcMonitor.bottom - MonitorInfo.rcMonitor.top;
|
||||
return (void*)hGameMonitor;
|
||||
}
|
||||
|
||||
MonitorWidthPx = -1;
|
||||
MonitorHeightPx = -1;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* FTobiiPlatformSpecific::MonitorHandleFromDeviceName(FString DeviceName)
|
||||
{
|
||||
MonitorHandleContext Context;
|
||||
Context.DeviceName = DeviceName;
|
||||
Context.hMonitorHandle = nullptr;
|
||||
EnumDisplayMonitors(nullptr, nullptr, EnumDisplayMonitorsProc, (LPARAM)&Context);
|
||||
return (void*)Context.hMonitorHandle;
|
||||
}
|
||||
|
||||
bool FTobiiPlatformSpecific::ConvertGazeCoordinateToVirtualDesktopPixel(void* GameWindowHandle, const FVector2D& ClientCoordsUNorm, FIntPoint& OutVirtualDesktopPixel)
|
||||
{
|
||||
//Now we can try to convert our point
|
||||
RECT ClientRect;
|
||||
if (GetClientRect((HWND)GameWindowHandle, &ClientRect))
|
||||
{
|
||||
POINT ClientPoint;
|
||||
ClientPoint.x = (int32)(ClientCoordsUNorm.X * (ClientRect.right - ClientRect.left));
|
||||
ClientPoint.y = (int32)(ClientCoordsUNorm.Y * (ClientRect.bottom - ClientRect.top));
|
||||
if (ClientToScreen((HWND)GameWindowHandle, &ClientPoint))
|
||||
{
|
||||
OutVirtualDesktopPixel.X = ClientPoint.x;
|
||||
OutVirtualDesktopPixel.Y = ClientPoint.y;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
FTobiiPlatformNotifications::FTobiiPlatformNotifications()
|
||||
: bShouldForceEyetrackerReconnect(true)
|
||||
, bShouldUpdateGameMonitorHandle(true)
|
||||
{
|
||||
if (FSlateApplication::IsInitialized())
|
||||
{
|
||||
const TSharedPtr<GenericApplication> Application = FSlateApplication::Get().GetPlatformApplication();
|
||||
if (Application.IsValid())
|
||||
{
|
||||
FWindowsApplication* WindowsApplication = (FWindowsApplication*)Application.Get();
|
||||
WindowsApplication->AddMessageHandler(*this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FTobiiPlatformNotifications::~FTobiiPlatformNotifications()
|
||||
{
|
||||
if (FSlateApplication::IsInitialized())
|
||||
{
|
||||
const TSharedPtr<GenericApplication> Application = FSlateApplication::Get().GetPlatformApplication();
|
||||
if (Application.IsValid())
|
||||
{
|
||||
FWindowsApplication* WindowsApplication = (FWindowsApplication*)Application.Get();
|
||||
WindowsApplication->RemoveMessageHandler(*this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FTobiiPlatformNotifications::ProcessMessage(HWND hwnd, uint32 msg, WPARAM wParam, LPARAM lParam, int32& OutResult)
|
||||
{
|
||||
switch (msg)
|
||||
{
|
||||
case WM_DEVICECHANGE:
|
||||
{
|
||||
if (wParam == DBT_DEVICEARRIVAL)
|
||||
{
|
||||
//This is narrow enough for us. We just want to short-circuit the reconnection timer if someone plugged in an eyetracker so they don't have to wait the full reconnection time.
|
||||
bShouldForceEyetrackerReconnect = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_DISPLAYCHANGE:
|
||||
{
|
||||
bShouldUpdateGameMonitorHandle = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#include "Runtime/Core/Public/Windows/HideWindowsPlatformTypes.h"
|
||||
#endif
|
||||
39
Source/TobiiCore/Private/TobiiPlatformSpecific.h
Normal file
39
Source/TobiiCore/Private/TobiiPlatformSpecific.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2017- Tobii Technology AB. All rights reserved.
|
||||
*
|
||||
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
class FTobiiPlatformSpecific
|
||||
{
|
||||
public:
|
||||
static void* GetMonitorInformation(const void* GameWindowHandle, int& MonitorWidthPx, int& MonitorHeightPx);
|
||||
static void* MonitorHandleFromDeviceName(FString DeviceName);
|
||||
|
||||
static bool ConvertGazeCoordinateToVirtualDesktopPixel(void* GameWindowHandle, const FVector2D& ClientCoordsUNorm, FIntPoint& OutVirtualDesktopPixel);
|
||||
};
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
#include "Windows/WindowsApplication.h"
|
||||
#include "HAL/ThreadSafeBool.h"
|
||||
|
||||
class FTobiiPlatformNotifications : public IWindowsMessageHandler
|
||||
{
|
||||
public:
|
||||
FTobiiPlatformNotifications();
|
||||
virtual ~FTobiiPlatformNotifications();
|
||||
|
||||
virtual bool ProcessMessage(HWND hwnd, uint32 msg, WPARAM wParam, LPARAM lParam, int32& OutResult) override;
|
||||
|
||||
public:
|
||||
FThreadSafeBool bShouldForceEyetrackerReconnect;
|
||||
FThreadSafeBool bShouldUpdateGameMonitorHandle;
|
||||
|
||||
};
|
||||
#elif PLATFORM_MAC
|
||||
#else
|
||||
#endif
|
||||
154
Source/TobiiCore/Private/UserGuide/STobiiLicenseWindow.cpp
Normal file
154
Source/TobiiCore/Private/UserGuide/STobiiLicenseWindow.cpp
Normal file
@@ -0,0 +1,154 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2017- Tobii Technology AB. All rights reserved.
|
||||
*
|
||||
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
|
||||
******************************************************************************/
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include "STobiiLicenseWindow.h"
|
||||
#include "STobiiWelcomeWindow.h"
|
||||
|
||||
#include "CoreGlobals.h"
|
||||
#include "Editor.h"
|
||||
#include "UnrealClient.h"
|
||||
#include "EditorStyleSet.h"
|
||||
#include "Framework/Application/SlateApplication.h"
|
||||
#include "Misc/ConfigCacheIni.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "Widgets/Layout/SGridPanel.h"
|
||||
#include "Widgets/Layout/SScrollBox.h"
|
||||
#include "Widgets/Images/SImage.h"
|
||||
#include "Widgets/Text/STextBlock.h"
|
||||
#include "Widgets/Input/SButton.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "TobiiLicenseWindow"
|
||||
|
||||
void STobiiLicenseWindow::Construct(const FArguments& InArgs, FTobiiCoreModule* InCoreModule)
|
||||
{
|
||||
if (GEditor == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CoreModule = InCoreModule;
|
||||
|
||||
#if TOBII_COMPILE_AS_ENGINE_PLUGIN
|
||||
FString LicenseFilePath = FPaths::EnginePluginsDir() / TEXT("Runtime/TobiiEyetracking/Resources/License.txt");
|
||||
#else
|
||||
FString LicenseFilePath = FPaths::ProjectPluginsDir() / TEXT("TobiiEyetracking/Resources/License.txt");
|
||||
#endif
|
||||
|
||||
FString LicenseText;
|
||||
bool bCanAccept = true;
|
||||
if (!FFileHelper::LoadFileToString(LicenseText, *LicenseFilePath))
|
||||
{
|
||||
LicenseText = "License file not found in Resources folder!\n\nAccept button disabled.\nIf you cannot repair/replace the Tobii SDK, please remove it from your project.";
|
||||
bCanAccept = false;
|
||||
}
|
||||
|
||||
SWindow::Construct(SWindow::FArguments()
|
||||
.SupportsMaximize(false)
|
||||
.SupportsMinimize(false)
|
||||
.IsPopupWindow(true)
|
||||
.CreateTitleBar(false)
|
||||
.SizingRule(ESizingRule::FixedSize)
|
||||
.SupportsTransparency(EWindowTransparency::None)
|
||||
.InitialOpacity(1.0f)
|
||||
.FocusWhenFirstShown(true)
|
||||
.bDragAnywhere(false)
|
||||
.ActivationPolicy(EWindowActivationPolicy::FirstShown)
|
||||
.ClientSize(FVector2D(1024, 768))
|
||||
.ScreenPosition(FVector2D((float)(GEditor->GetActiveViewport()->GetSizeXY().X) / 2.0,
|
||||
(float)(GEditor->GetActiveViewport()->GetSizeXY().Y) / 2.0))
|
||||
[
|
||||
SNew(SVerticalBox)
|
||||
+ SVerticalBox::Slot()
|
||||
.FillHeight(0.9f)
|
||||
.Padding(5)
|
||||
[
|
||||
SNew(SScrollBox)
|
||||
+SScrollBox::Slot()
|
||||
[
|
||||
SNew(SGridPanel)
|
||||
.FillColumn(0, 1.0f)
|
||||
.FillRow(0, 1.0f)
|
||||
|
||||
+ SGridPanel::Slot(0, 0)
|
||||
[
|
||||
SNew(SImage)
|
||||
.ColorAndOpacity(FSlateColor(FLinearColor(FColor::Black)))
|
||||
]
|
||||
|
||||
+ SGridPanel::Slot(0, 0)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.AutoWrapText(true)
|
||||
.Margin(10.0f)
|
||||
.Text(FText::FromString(LicenseText))
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
+ SVerticalBox::Slot()
|
||||
.FillHeight(0.05f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.VAlign(VAlign_Center)
|
||||
.HAlign(HAlign_Fill)
|
||||
.IsEnabled(bCanAccept)
|
||||
.OnClicked(this, &STobiiLicenseWindow::AcceptLicense)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("OkButton", "I Accept the terms of this agreement"))
|
||||
.Justification(ETextJustify::Center)
|
||||
]
|
||||
]
|
||||
+ SVerticalBox::Slot()
|
||||
.FillHeight(0.05f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.VAlign(VAlign_Center)
|
||||
.HAlign(HAlign_Fill)
|
||||
.OnClicked(this, &STobiiLicenseWindow::ShutdownEditor)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("ExitButton", "I do not accept the terms. Shut down editor so I can remove the plugin."))
|
||||
.Justification(ETextJustify::Center)
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
bIsTopmostWindow = true;
|
||||
FlashWindow();
|
||||
}
|
||||
|
||||
FReply STobiiLicenseWindow::AcceptLicense()
|
||||
{
|
||||
RequestDestroyWindow();
|
||||
|
||||
if (GConfig != nullptr)
|
||||
{
|
||||
GConfig->SetBool(TEXT("Tobii"), TEXT("TobiiLicenseAccepted"), true, GGameIni);
|
||||
}
|
||||
|
||||
bool bTutorialShown = false;
|
||||
GConfig->GetBool(TEXT("Tobii"), TEXT("TobiiTutorialShown"), bTutorialShown, GGameIni);
|
||||
if (!bTutorialShown && GEditor != nullptr)
|
||||
{
|
||||
FSlateApplication::Get().AddWindow(SNew(STobiiWelcomeWindow));
|
||||
}
|
||||
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply STobiiLicenseWindow::ShutdownEditor()
|
||||
{
|
||||
RequestDestroyWindow();
|
||||
FGenericPlatformMisc::RequestExit(false);
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
#endif //WITH_EDITOR
|
||||
36
Source/TobiiCore/Private/UserGuide/STobiiLicenseWindow.h
Normal file
36
Source/TobiiCore/Private/UserGuide/STobiiLicenseWindow.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2017- Tobii Technology AB. All rights reserved.
|
||||
*
|
||||
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include "../TobiiCoreModule.h"
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SWebBrowser.h"
|
||||
#include "Widgets/SWindow.h"
|
||||
|
||||
class STobiiLicenseWindow : public SWindow
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(STobiiLicenseWindow) { }
|
||||
SLATE_END_ARGS()
|
||||
|
||||
STobiiLicenseWindow() {}
|
||||
|
||||
/** Widget constructor */
|
||||
void Construct(const FArguments& Args, FTobiiCoreModule* InCoreModule);
|
||||
|
||||
private:
|
||||
TSharedPtr<SWebBrowser> MainBrowser;
|
||||
FTobiiCoreModule* CoreModule;
|
||||
|
||||
FReply AcceptLicense();
|
||||
FReply ShutdownEditor();
|
||||
};
|
||||
|
||||
#endif //WITH_EDITOR
|
||||
214
Source/TobiiCore/Private/UserGuide/STobiiWelcomeWindow.cpp
Normal file
214
Source/TobiiCore/Private/UserGuide/STobiiWelcomeWindow.cpp
Normal file
@@ -0,0 +1,214 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2017- Tobii Technology AB. All rights reserved.
|
||||
*
|
||||
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
|
||||
******************************************************************************/
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include "STobiiWelcomeWindow.h"
|
||||
|
||||
#include "Editor.h"
|
||||
#include "EditorStyleSet.h"
|
||||
#include "ContentBrowserModule.h"
|
||||
#include "IContentBrowserSingleton.h"
|
||||
#include "CoreGlobals.h"
|
||||
#include "UnrealClient.h"
|
||||
#include "Widgets/Layout/SGridPanel.h"
|
||||
#include "Widgets/Layout/SScrollBox.h"
|
||||
#include "Widgets/Images/SImage.h"
|
||||
#include "Widgets/Text/STextBlock.h"
|
||||
#include "Widgets/Input/SButton.h"
|
||||
#include "Widgets/Input/SCheckBox.h"
|
||||
#include "Misc/ConfigCacheIni.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "STobiiWelcomeWindow"
|
||||
|
||||
void STobiiWelcomeWindow::Construct(const FArguments& InArgs)
|
||||
{
|
||||
if (GEditor == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bDontShowAgain = false;
|
||||
|
||||
SWindow::Construct(SWindow::FArguments()
|
||||
.SupportsMaximize(false)
|
||||
.SupportsMinimize(false)
|
||||
.IsPopupWindow(true)
|
||||
.CreateTitleBar(false)
|
||||
.SizingRule(ESizingRule::FixedSize)
|
||||
.SupportsTransparency(EWindowTransparency::None)
|
||||
.InitialOpacity(1.0f)
|
||||
.FocusWhenFirstShown(true)
|
||||
.bDragAnywhere(false)
|
||||
.ActivationPolicy(EWindowActivationPolicy::FirstShown)
|
||||
.ClientSize(FVector2D(600, 400))
|
||||
.ScreenPosition(FVector2D((float)(GEditor->GetActiveViewport()->GetSizeXY().X) / 2.0,
|
||||
(float)(GEditor->GetActiveViewport()->GetSizeXY().Y) / 2.0))
|
||||
[
|
||||
SNew(SGridPanel)
|
||||
.FillColumn(0, 1.0f)
|
||||
.FillRow(0, 1.0f)
|
||||
|
||||
+ SGridPanel::Slot(0, 0)
|
||||
[
|
||||
SNew(SImage)
|
||||
.ColorAndOpacity(FSlateColor(FLinearColor(FColor::Black)))
|
||||
]
|
||||
|
||||
+ SGridPanel::Slot(0, 0)
|
||||
[
|
||||
SNew(SVerticalBox)
|
||||
+ SVerticalBox::Slot()
|
||||
.Padding(10.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Justification(ETextJustify::Center)
|
||||
.Margin(5.0f)
|
||||
.Font(FEditorStyle::GetFontStyle(FName("ToggleButton.LabelFont")))
|
||||
.Text(FText::FromString("Tobii Sample Content"))
|
||||
]
|
||||
|
||||
+ SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.AutoWrapText(true)
|
||||
.Margin(5.0f)
|
||||
.Text(FText::FromString("Thank you again for downloading the UE4 Tobii eyetracking plugin!.\nThis plugin comes loaded with sample content as well as the core functionality to enable your development environment to leverage the eyetracker hardware.\nThe sample content is packaged can be accessed via the content browser under the 'TobiiEyetracking' plugin section. As plugin content is not visible by default however, you need to first click the small eye button at the bottom right of your content browser and check the 'show plugin content' checkbox. After that you should be able to scroll your content browser down to see the category."))
|
||||
]
|
||||
|
||||
+ SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(15.0f)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
[
|
||||
SNew(SCheckBox)
|
||||
.IsChecked(bDontShowAgain ? ECheckBoxState::Checked : ECheckBoxState::Unchecked)
|
||||
.OnCheckStateChanged_Lambda([this](ECheckBoxState State) { bDontShowAgain = State == ECheckBoxState::Checked; })
|
||||
]
|
||||
+SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString("Don't show this window again."))
|
||||
]
|
||||
]
|
||||
|
||||
+ SVerticalBox::Slot()
|
||||
.Padding(0.0f, 50.0f, 0.0f, 0.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString("Would you like to navigate to the samples now?"))
|
||||
]
|
||||
|
||||
+ SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
[
|
||||
SNew(SButton)
|
||||
.VAlign(VAlign_Center)
|
||||
.HAlign(HAlign_Fill)
|
||||
.OnClicked(this, &STobiiWelcomeWindow::ShowDesktopSamples)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("OkButton", "Yes, please show me the desktop samples"))
|
||||
.Justification(ETextJustify::Center)
|
||||
]
|
||||
]
|
||||
+ SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
[
|
||||
SNew(SButton)
|
||||
.VAlign(VAlign_Center)
|
||||
.HAlign(HAlign_Fill)
|
||||
.OnClicked(this, &STobiiWelcomeWindow::ShowXRSamples)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("OkButtonXR", "Yes, please show me the XR samples"))
|
||||
.Justification(ETextJustify::Center)
|
||||
]
|
||||
]
|
||||
+ SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
[
|
||||
SNew(SButton)
|
||||
.VAlign(VAlign_Center)
|
||||
.HAlign(HAlign_Fill)
|
||||
.OnClicked(this, &STobiiWelcomeWindow::AbortTutorial)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("ExitButton", "No thank you."))
|
||||
.Justification(ETextJustify::Center)
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
bIsTopmostWindow = true;
|
||||
FlashWindow();
|
||||
}
|
||||
|
||||
void STobiiWelcomeWindow::UpdateFlag()
|
||||
{
|
||||
if (GConfig != nullptr && bDontShowAgain)
|
||||
{
|
||||
GConfig->SetBool(TEXT("Tobii"), TEXT("TobiiTutorialShown"), true, GGameIni);
|
||||
}
|
||||
}
|
||||
|
||||
FReply STobiiWelcomeWindow::ShowDesktopSamples()
|
||||
{
|
||||
RequestDestroyWindow();
|
||||
UpdateFlag();
|
||||
|
||||
//Actually turn on plugin content viewing and navigate the content browser to the sample
|
||||
IContentBrowserSingleton& ContentBrowserInterface = FModuleManager::LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser")).Get();
|
||||
ContentBrowserInterface.ForceShowPluginContent(false);
|
||||
|
||||
TArray<FAssetData> MapAsset;
|
||||
MapAsset.Add(FAssetData(FName("/TobiiEyetracking/Sample/Desktop/DesktopExampleMap")
|
||||
, FName("/TobiiEyetracking/Sample/Desktop")
|
||||
, FName("DesktopExampleMap")
|
||||
, FName("World")));
|
||||
ContentBrowserInterface.SyncBrowserToAssets(MapAsset);
|
||||
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply STobiiWelcomeWindow::ShowXRSamples()
|
||||
{
|
||||
RequestDestroyWindow();
|
||||
UpdateFlag();
|
||||
|
||||
//Actually turn on plugin content viewing and navigate the content browser to the sample
|
||||
IContentBrowserSingleton& ContentBrowserInterface = FModuleManager::LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser")).Get();
|
||||
ContentBrowserInterface.ForceShowPluginContent(false);
|
||||
|
||||
TArray<FAssetData> MapAsset;
|
||||
MapAsset.Add(FAssetData(FName("/TobiiEyetracking/Sample/XR/XRExampleMap")
|
||||
, FName("/TobiiEyetracking/Sample/XR")
|
||||
, FName("XRExampleMap")
|
||||
, FName("World")));
|
||||
ContentBrowserInterface.SyncBrowserToAssets(MapAsset);
|
||||
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply STobiiWelcomeWindow::AbortTutorial()
|
||||
{
|
||||
RequestDestroyWindow();
|
||||
UpdateFlag();
|
||||
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
#endif //WITH_EDITOR
|
||||
38
Source/TobiiCore/Private/UserGuide/STobiiWelcomeWindow.h
Normal file
38
Source/TobiiCore/Private/UserGuide/STobiiWelcomeWindow.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2017- Tobii Technology AB. All rights reserved.
|
||||
*
|
||||
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SWebBrowser.h"
|
||||
#include "Widgets/SWindow.h"
|
||||
|
||||
class STobiiWelcomeWindow : public SWindow
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(STobiiWelcomeWindow)
|
||||
{
|
||||
}
|
||||
SLATE_END_ARGS()
|
||||
|
||||
STobiiWelcomeWindow() {}
|
||||
|
||||
/** Widget constructor */
|
||||
void Construct(const FArguments& Args);
|
||||
|
||||
private:
|
||||
TSharedPtr<SWebBrowser> MainBrowser;
|
||||
bool bDontShowAgain;
|
||||
|
||||
void UpdateFlag();
|
||||
FReply ShowDesktopSamples();
|
||||
FReply ShowXRSamples();
|
||||
FReply AbortTutorial();
|
||||
};
|
||||
|
||||
#endif //WITH_EDITOR
|
||||
88
Source/TobiiCore/Private/UserGuide/TobiiEditorExtension.cpp
Normal file
88
Source/TobiiCore/Private/UserGuide/TobiiEditorExtension.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2017- Tobii Technology AB. All rights reserved.
|
||||
*
|
||||
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
|
||||
******************************************************************************/
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include "TobiiEditorExtension.h"
|
||||
#include "STobiiLicenseWindow.h"
|
||||
|
||||
#include "RHI.h"
|
||||
#include "Editor.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "Framework/Application/SlateApplication.h"
|
||||
|
||||
FTobiiEditorExtension::FTobiiEditorExtension(FTobiiCoreModule* InCoreModule)
|
||||
: CoreModule(InCoreModule)
|
||||
, bIsEditorInitialized(false)
|
||||
{
|
||||
// Defer Level Editor UI extensions until Level Editor has been loaded:
|
||||
if (FModuleManager::Get().IsModuleLoaded("LevelEditor"))
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
else
|
||||
{
|
||||
FModuleManager::Get().OnModulesChanged().AddLambda([this](FName name, EModuleChangeReason reason)
|
||||
{
|
||||
if ((name == "LevelEditor") && (reason == EModuleChangeReason::ModuleLoaded))
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
FTobiiEditorExtension::~FTobiiEditorExtension()
|
||||
{
|
||||
}
|
||||
|
||||
void FTobiiEditorExtension::Initialize()
|
||||
{
|
||||
if (GUsingNullRHI)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FSlateRenderer* SlateRenderer = FSlateApplication::Get().GetRenderer();
|
||||
if (SlateRenderer != nullptr)
|
||||
{
|
||||
LoadedDelegateHandle = SlateRenderer->OnSlateWindowRendered().AddRaw(this, &FTobiiEditorExtension::OnEditorLoaded);
|
||||
}
|
||||
}
|
||||
|
||||
void FTobiiEditorExtension::OnEditorLoaded(SWindow& SlateWindow, void* ViewportRHIPtr)
|
||||
{
|
||||
// would be nice to use the preprocessor definition WITH_EDITOR instead,
|
||||
// but the user may launch a standalone game through the editor...
|
||||
if (GEditor == nullptr || CoreModule == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsInGameThread())
|
||||
{
|
||||
FSlateRenderer* SlateRenderer = FSlateApplication::Get().GetRenderer();
|
||||
SlateRenderer->OnSlateWindowRendered().Remove(LoadedDelegateHandle);
|
||||
}
|
||||
|
||||
if (bIsEditorInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bIsEditorInitialized = true;
|
||||
|
||||
if (GConfig != nullptr)
|
||||
{
|
||||
bool bTobiiLicenseAccepted(false);
|
||||
GConfig->GetBool(TEXT("Tobii"), TEXT("TobiiLicenseAccepted"), bTobiiLicenseAccepted, GGameIni);
|
||||
if (!bTobiiLicenseAccepted && GEditor != nullptr)
|
||||
{
|
||||
FSlateApplication::Get().AddWindow(SNew(STobiiLicenseWindow, CoreModule));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif //WITH_EDITOR
|
||||
30
Source/TobiiCore/Private/UserGuide/TobiiEditorExtension.h
Normal file
30
Source/TobiiCore/Private/UserGuide/TobiiEditorExtension.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2017- Tobii Technology AB. All rights reserved.
|
||||
*
|
||||
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include "../TobiiCoreModule.h"
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
class FTobiiEditorExtension
|
||||
{
|
||||
public:
|
||||
FTobiiEditorExtension(FTobiiCoreModule* InCoreModule);
|
||||
~FTobiiEditorExtension();
|
||||
void Initialize();
|
||||
|
||||
private:
|
||||
FTobiiCoreModule* CoreModule;
|
||||
FDelegateHandle LoadedDelegateHandle;
|
||||
bool bIsEditorInitialized;
|
||||
|
||||
void OnEditorLoaded(SWindow& SlateWindow, void* ViewportRHIPtr);
|
||||
};
|
||||
|
||||
#endif //WITH_EDITOR
|
||||
59
Source/TobiiCore/Public/ITobiiCore.h
Normal file
59
Source/TobiiCore/Public/ITobiiCore.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2017- Tobii Technology AB. All rights reserved.
|
||||
*
|
||||
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ITobiiEyetracker.h"
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "IEyeTrackerModule.h"
|
||||
#include "Modules/ModuleInterface.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
DECLARE_DELEGATE(FPostLoadedTobiiEyetrackingDelegate);
|
||||
|
||||
/**
|
||||
* The public interface of the TobiiEyeX Module.
|
||||
*/
|
||||
class ITobiiCore : public IEyeTrackerModule
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Singleton-like access to this module's interface. This is just for convenience!
|
||||
* Beware of calling this during the shutdown phase, though. Your module might have been
|
||||
* unloaded already.
|
||||
*
|
||||
* @return Returns singleton instance, loading the module on demand if needed
|
||||
*/
|
||||
static inline ITobiiCore& Get()
|
||||
{
|
||||
return FModuleManager::LoadModuleChecked<ITobiiCore>("TobiiCore");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if this module is loaded and ready. It is only valid to call Get() if
|
||||
* IsAvailable() returns true.
|
||||
*
|
||||
* @return True if the module is loaded and ready to use
|
||||
*/
|
||||
static inline bool IsAvailable()
|
||||
{
|
||||
return FModuleManager::Get().IsModuleLoaded("TobiiCore") && Get().GetEyeTrackerInternal().IsValid();
|
||||
}
|
||||
|
||||
virtual FString GetModuleKeyName() const override
|
||||
{
|
||||
return FString("TobiiCore");
|
||||
}
|
||||
|
||||
static TSharedPtr<ITobiiEyeTracker, ESPMode::ThreadSafe> GetEyeTracker()
|
||||
{
|
||||
return IsAvailable() ? Get().GetEyeTrackerInternal() : TSharedPtr<ITobiiEyeTracker, ESPMode::ThreadSafe>(nullptr);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual TSharedPtr<ITobiiEyeTracker, ESPMode::ThreadSafe> GetEyeTrackerInternal() = 0;
|
||||
};
|
||||
118
Source/TobiiCore/Public/ITobiiEyetracker.h
Normal file
118
Source/TobiiCore/Public/ITobiiEyetracker.h
Normal file
@@ -0,0 +1,118 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2017- Tobii Technology AB. All rights reserved.
|
||||
*
|
||||
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "TobiiTypes.h"
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "IEyeTracker.h"
|
||||
|
||||
class ITobiiEyeTracker : public IEyeTracker
|
||||
{
|
||||
/************************************************************************/
|
||||
/* Common Data */
|
||||
/************************************************************************/
|
||||
public:
|
||||
/**
|
||||
* Will return gaze data for a combination of both eyes.
|
||||
* For mono trackers this might mean sending the data from the active eye.
|
||||
* For a more advanced system, it might be some combination of both eyes.
|
||||
* It contains all kinds of data you might need for low level interaction.
|
||||
*
|
||||
* @returns Combined gaze data.
|
||||
*/
|
||||
virtual const FTobiiGazeData& GetCombinedGazeData() const = 0;
|
||||
|
||||
/**
|
||||
* Will return the gaze data for the left eye.
|
||||
* It contains all kinds of data you might need for low level interaction.
|
||||
*
|
||||
* @returns Gaze data for the right eye.
|
||||
*/
|
||||
virtual const FTobiiGazeData& GetLeftGazeData() const = 0;
|
||||
|
||||
/**
|
||||
* Will return the gaze data for the right eye.
|
||||
* It contains all kinds of data you might need for low level interaction.
|
||||
*
|
||||
* @returns Gaze data for the left eye.
|
||||
*/
|
||||
virtual const FTobiiGazeData& GetRightGazeData() const = 0;
|
||||
|
||||
/**
|
||||
* Returns information about the status of the current device.
|
||||
*
|
||||
* @return The status of the device.
|
||||
*/
|
||||
virtual ETobiiGazeTrackerStatus GetGazeTrackerStatus() const = 0;
|
||||
|
||||
/**
|
||||
* Some gaze trackers might only be able to deliver data for one eye, and others might be able to serve data for both.
|
||||
* If you are planning to use other functions than GetCombinedGazeData, checking that the gaze tracker you are working with supports them is recommended.
|
||||
*
|
||||
* @param Capability The capability to poll.
|
||||
* @returns Whether the gaze tracker supports the capability polled.
|
||||
*/
|
||||
virtual bool GetGazeTrackerCapability(ETobiiGazeTrackerCapability Capability) const = 0;
|
||||
|
||||
/**
|
||||
* This is information about where the combined gaze ray hits the world. As this is needed by many different gaze related features, we do this raycast for the developer.
|
||||
*
|
||||
* @returns Relevant hit result.
|
||||
*/
|
||||
virtual const FHitResult& GetCombinedWorldGazeHitData() const = 0;
|
||||
|
||||
/**
|
||||
* This is information about where the combined gaze ray hits the world. As this is needed by many different gaze related features, we do this raycast for the developer.
|
||||
*
|
||||
* @returns Relevant hit result.
|
||||
*/
|
||||
virtual const FHitResult& GetLeftWorldGazeHitData() const = 0;
|
||||
|
||||
/**
|
||||
* This is information about where the combined gaze ray hits the world. As this is needed by many different gaze related features, we do this raycast for the developer.
|
||||
*
|
||||
* @returns Relevant hit result.
|
||||
*/
|
||||
virtual const FHitResult& GetRightWorldGazeHitData() const = 0;
|
||||
|
||||
/**
|
||||
* Knowledge about the monitor size when designing gaze interactions on desktop can be very useful since working in SI units can make code screen size invariant. Display information is also useful for foveation techniques both in desktop and XR.
|
||||
*
|
||||
* @returns Information regarding the display associated with the currently active tracker.
|
||||
*/
|
||||
virtual const FTobiiDisplayInfo& GetDisplayInformation() const = 0;
|
||||
|
||||
/************************************************************************/
|
||||
/* Head Tracker */
|
||||
/************************************************************************/
|
||||
public:
|
||||
/**
|
||||
* This gives you all the relevant data for head tracking
|
||||
*
|
||||
* @returns Information regarding head position and orientation
|
||||
*/
|
||||
virtual const FTobiiHeadPoseData& GetHeadPoseData() const = 0;
|
||||
|
||||
/************************************************************************/
|
||||
/* Desktop */
|
||||
/************************************************************************/
|
||||
public:
|
||||
/**
|
||||
* On desktop, the tracker can only track the user within a limited frustum.
|
||||
*
|
||||
* @returns The vertices defining the tracking frustum in the Tobii User Coordinate Space.
|
||||
*/
|
||||
virtual const FTobiiDesktopTrackBox& GetDesktopTrackBox() const = 0;
|
||||
|
||||
/**
|
||||
* This is output from the Tobii Infinite screen algorithm. You ideally shouldn't use these angles by yourself, but instead use the camera modifier found in the interaction module.
|
||||
*
|
||||
* @returns Infinite screen angles.
|
||||
*/
|
||||
virtual const FRotator& GetInfiniteScreenAngles() const = 0;
|
||||
};
|
||||
153
Source/TobiiCore/Public/TobiiBlueprintLibrary.h
Normal file
153
Source/TobiiCore/Public/TobiiBlueprintLibrary.h
Normal file
@@ -0,0 +1,153 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2017- Tobii Technology AB. All rights reserved.
|
||||
*
|
||||
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "TobiiTypes.h"
|
||||
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "Components/WidgetComponent.h"
|
||||
|
||||
#include "TobiiBlueprintLibrary.generated.h"
|
||||
|
||||
/**
|
||||
* Simplified interface for blueprint use. Only exposes the features most likely to be consumed from BP.
|
||||
*/
|
||||
UCLASS()
|
||||
class TOBIICORE_API UTobiiBlueprintLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
/************************************************************************/
|
||||
/* Common functions */
|
||||
/************************************************************************/
|
||||
public:
|
||||
|
||||
/**
|
||||
* This is just a helper function to set the eyetracking enabled CVar for blueprint convenience.
|
||||
* Set eyetracking disabled if you don't need tracking and don't want to pay the CPU cost in some parts of your program.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Tobii Eyetracking")
|
||||
static void SetTobiiEyetrackingEnabled(bool bEyetrackingEnabled);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Tobii Eyetracking")
|
||||
static bool GetTobiiEyetrackingEnabled();
|
||||
|
||||
/**
|
||||
* This is just a helper function to set the eyetracking frozen CVar for blueprint convenience.
|
||||
* Freezing the eyetracking can be very useful for debugging as well as when trying to show someone what effect eyetracking has.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Tobii Eyetracking")
|
||||
static void SetTobiiEyetrackingFrozen(bool bEyetrackingFrozen);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Tobii Eyetracking")
|
||||
static bool GetTobiiEyetrackingFrozen();
|
||||
|
||||
/**
|
||||
* This is the main method to get basic eye tracking data. You should not be using this though for most things. Use the focus system instead if you can.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Tobii Eyetracking")
|
||||
static FTobiiGazeData GetTobiiCombinedGazeData();
|
||||
UFUNCTION(BlueprintPure, Category = "Tobii Eyetracking")
|
||||
static FTobiiGazeData GetTobiiLeftGazeData();
|
||||
UFUNCTION(BlueprintPure, Category = "Tobii Eyetracking")
|
||||
static FTobiiGazeData GetTobiiRightGazeData();
|
||||
UFUNCTION(BlueprintPure, Category = "Tobii Eyetracking")
|
||||
static FHitResult GetTobiiCombinedWorldGazeHitData();
|
||||
|
||||
/**
|
||||
* This will indicate the readiness of the gaze tracking subsystem. For most applications this will also indicate head tracking readiness.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Tobii Eyetracking")
|
||||
static ETobiiGazeTrackerStatus GetTobiiGazeTrackerStatus();
|
||||
|
||||
/**
|
||||
* Gets information about the display device and window used for the application.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Tobii Eyetracking")
|
||||
static FTobiiDisplayInfo GetTobiiDisplayInformation();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Tobii Eyetracking")
|
||||
static FRotator GetTobiiInfiniteScreenAngles();
|
||||
|
||||
/************************************************************************/
|
||||
/* Desktop functions */
|
||||
/************************************************************************/
|
||||
public:
|
||||
/**
|
||||
* This is the main method to get basic head pose data.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Tobii Desktop Eyetracking")
|
||||
static const FTobiiHeadPoseData& GetTobiiHeadPoseData();
|
||||
/**
|
||||
* Track box information.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Tobii Desktop Eyetracking")
|
||||
static const FTobiiDesktopTrackBox& GetTobiiDesktopTrackBox();
|
||||
|
||||
/************************************************************************/
|
||||
/* Settings functions */
|
||||
/************************************************************************/
|
||||
public:
|
||||
//Use these functions to get and set CVars in runtime.
|
||||
UFUNCTION(BlueprintPure, Category = "Tobii Settings")
|
||||
static int32 GetTobiiInt(FString CVarName);
|
||||
UFUNCTION(BlueprintCallable, Category = "Tobii Settings")
|
||||
static void SetTobiiInt(FString CVarName, const int32 NewValue);
|
||||
UFUNCTION(BlueprintPure, Category = "Tobii Settings")
|
||||
static float GetTobiiFloat(FString CVarName);
|
||||
UFUNCTION(BlueprintCallable, Category = "Tobii Settings")
|
||||
static void SetTobiiFloat(FString CVarName, const float NewValue);
|
||||
|
||||
//If you are persisting your custom CVar values, you should call this function when you start your application.
|
||||
UFUNCTION(BlueprintCallable, Category = "Tobii Settings")
|
||||
static void LoadTobiiSettings();
|
||||
//If you want to persist a CVar to .ini, use this function.
|
||||
UFUNCTION(BlueprintCallable, Category = "Tobii Settings")
|
||||
static void SaveTobiiSetting(FString CVarSettingToSave);
|
||||
|
||||
/************************************************************************/
|
||||
/* Math Util functions */
|
||||
/************************************************************************/
|
||||
public:
|
||||
/**
|
||||
* Use this function if you want to project slate information to viewport space. Includes DPI adjustments
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Tobii Math Utils")
|
||||
static bool VirtualDesktopPixelToViewportCoordinateUNorm(const FVector2D& VirtualDesktopPixel, FVector2D& OutViewportCoordinateUNorm);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Tobii Math Utils")
|
||||
static bool ViewportCoordinateUNormToVirtualDesktopPixel(const FVector2D& ViewportCoordinateUNorm, FVector2D& OutVirtualDesktopPixel);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Tobii Math Utils")
|
||||
static bool ViewportPixelCoordToCmCoord(const FVector2D& InCoordinatePx, FVector2D& OutCoordinateCm);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Tobii Math Utils")
|
||||
static bool ViewportCmCoordToPixelCoord(const FVector2D& InCoordinateCm, FVector2D& OutCoordinatePx);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Tobii Math Utils")
|
||||
static bool ViewportPixelCoordToUNormCoord(const FVector2D& InCoordinatePx, FVector2D& OutCoordinateUNorm);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Tobii Math Utils")
|
||||
static bool ViewportUNormCoordToPixelCoord(const FVector2D& InCoordinateUNorm, FVector2D& OutCoordinatePx);
|
||||
|
||||
/**
|
||||
* This function lets you move from world space to the local space of the user. In XR this would be head space, and for desktop it would be relative to the scene camera.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Tobii Math Utils")
|
||||
static FVector ConvertGazeToUserSpace(APlayerController* PlayerController, const FVector& WorldSpaceGazeDirection);
|
||||
|
||||
/************************************************************************/
|
||||
/* Util functions */
|
||||
/************************************************************************/
|
||||
public:
|
||||
/**
|
||||
* Tests if a given player controller is XR enabled
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Tobii XR Utils")
|
||||
static bool IsXRPlayerController(const APlayerController* PlayerController);
|
||||
};
|
||||
225
Source/TobiiCore/Public/TobiiTypes.h
Normal file
225
Source/TobiiCore/Public/TobiiTypes.h
Normal file
@@ -0,0 +1,225 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2017- Tobii Technology AB. All rights reserved.
|
||||
*
|
||||
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/EngineTypes.h"
|
||||
|
||||
#include "TobiiTypes.generated.h"
|
||||
|
||||
/**
|
||||
* Structure that contains gaze information from one eye in both screen and world space.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FTobiiGazeData
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
public:
|
||||
//Origin of the eye's gaze ray in Unreal world space.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "World Space Gaze Data")
|
||||
FVector WorldGazeOrigin;
|
||||
//Forward direction of the eye's gaze ray this frame in Unreal world space.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "World Space Gaze Data")
|
||||
FVector WorldGazeDirection;
|
||||
//Due to how the eye works and imperfections in eye tracking technology, it makes more sense to express the world gaze field as a cone rather than a ray. This angle is the angle between the Gaze Direction and the side of the cone expressed in degrees.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "World Space Data")
|
||||
float WorldGazeConeAngleDegrees;
|
||||
|
||||
//The gaze point in screen space in pixels this frame.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Screen Space Gaze Data")
|
||||
FVector2D ScreenGazePointPx;
|
||||
//Due to how the eye works and imperfections in eye tracking technology, it makes more sense to express the screen space gaze field as an ellipse rather than a point. This is the extent of that ellipse expressed in pixels for the eye.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Screen Space Data")
|
||||
FVector2D ScreenGazeCircleRadiiPx;
|
||||
|
||||
//Time when the gaze point was created.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General Data")
|
||||
FDateTime TimeStamp;
|
||||
//This is how open the eye is. 0 means closed, 1 means open.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General Data")
|
||||
float EyeOpenness;
|
||||
//If the gaze point is moving slowly enough, it is considered stable. This is useful when trying to prevent false positives when the gaze point is moving past objects.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General Data")
|
||||
bool bIsStable;
|
||||
//If this is true, all other properties related to the eye should be safe to use. This might be false because the tracking system is a mono tracker, or if the data is bad for this frame.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "General Data")
|
||||
bool bIsGazeDataValid;
|
||||
|
||||
FTobiiGazeData()
|
||||
: bIsGazeDataValid(false)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Location and orientation of the head.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FTobiiHeadPoseData
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
public:
|
||||
FTobiiHeadPoseData()
|
||||
: HeadLocation(0.0f, 0.0f, 0.0f)
|
||||
, HeadOrientation(0.0f, 0.0f, 0.0f)
|
||||
{}
|
||||
|
||||
//This is the local space location of the head relative to the device specified origin in centimeters.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Head Pose Data")
|
||||
FVector HeadLocation;
|
||||
//This is the local space orientation of the head.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Head Pose Data")
|
||||
FRotator HeadOrientation;
|
||||
};
|
||||
|
||||
/**
|
||||
* Information regarding the display associated with the currently active eye tracker.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FTobiiDisplayInfo
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
public:
|
||||
//This is the width in pixels of the monitor the game is running on as reported by windows.
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Display Info")
|
||||
int32 MonitorWidthPx;
|
||||
//This is the height in pixels of the monitor the game is running on as reported by windows.
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Display Info")
|
||||
int32 MonitorHeightPx;
|
||||
//This is the width in centimeters of the monitor the active eyetracker is attached to as reported by the eyetracker. Centimeters is also the measurement used for unreal units.
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Display Info")
|
||||
float MonitorWidthCm;
|
||||
//This is the height in centimeters of the monitor the active eyetracker is attached to as reported by the eyetracker. Centimeters is also the measurement used for unreal units.
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Display Info")
|
||||
float MonitorHeightCm;
|
||||
|
||||
//This is the width in pixels of the main game viewport as reported by the game.
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Display Info")
|
||||
int32 MainViewportWidthPx;
|
||||
//This is the height in pixels of the main game viewport as reported by the game.
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Display Info")
|
||||
int32 MainViewportHeightPx;
|
||||
//This is the width of the main game viewport in centimeters as calculated using data from the game and the eyetracker. Centimeters is also the measurement used for unreal units.
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Display Info")
|
||||
float MainViewportWidthCm;
|
||||
//This is the height of the main game viewport in centimeters as calculated using data from the game and the eyetracker. Centimeters is also the measurement used for unreal units.
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Display Info")
|
||||
float MainViewportHeightCm;
|
||||
|
||||
//This is the dpi scale of the monitor the game is running on
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Display Info")
|
||||
float DpiScale;
|
||||
|
||||
public:
|
||||
void* GameWindowHandle;
|
||||
void* GameMonitorHandle;
|
||||
|
||||
public:
|
||||
FTobiiDisplayInfo()
|
||||
: MonitorWidthPx(0)
|
||||
, MonitorHeightPx(0)
|
||||
, MonitorWidthCm(0.0f)
|
||||
, MonitorHeightCm(0.0f)
|
||||
|
||||
, MainViewportWidthPx(0)
|
||||
, MainViewportHeightPx(0)
|
||||
, MainViewportWidthCm(0.0f)
|
||||
, MainViewportHeightCm(0.0f)
|
||||
|
||||
, DpiScale(1.0f)
|
||||
|
||||
, GameWindowHandle(nullptr)
|
||||
, GameMonitorHandle(nullptr)
|
||||
{}
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the vertices for the desktop only track box expressed in Tobii User Coordinate System coordinates.
|
||||
* This coordinate system has its origin in the middle of the active display. X+ is towards the user's right. Y+ is up. Z+ is away from the screen towards the user.
|
||||
* See general API documentation for details.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FTobiiDesktopTrackBox
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
public:
|
||||
//These are the extreme points of the eye tracker tracking frustum where we get optimal tracking quality in centimeters. These are in the Tobii User Coordinate System.
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Track Box")
|
||||
FVector FrontUpperRightPointCm;
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Track Box")
|
||||
FVector FrontUpperLeftPointCm;
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Track Box")
|
||||
FVector FrontLowerLeftPointCm;
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Track Box")
|
||||
FVector FrontLowerRightPointCm;
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Track Box")
|
||||
FVector BackUpperRightPointCm;
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Track Box")
|
||||
FVector BackUpperLeftPointCm;
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Track Box")
|
||||
FVector BackLowerLeftPointCm;
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Track Box")
|
||||
FVector BackLowerRightPointCm;
|
||||
|
||||
public:
|
||||
FTobiiDesktopTrackBox()
|
||||
: FrontUpperRightPointCm(0.0f, 0.0f, 0.0f)
|
||||
, FrontUpperLeftPointCm(0.0f, 0.0f, 0.0f)
|
||||
, FrontLowerLeftPointCm(0.0f, 0.0f, 0.0f)
|
||||
, FrontLowerRightPointCm(0.0f, 0.0f, 0.0f)
|
||||
, BackUpperRightPointCm(0.0f, 0.0f, 0.0f)
|
||||
, BackUpperLeftPointCm(0.0f, 0.0f, 0.0f)
|
||||
, BackLowerLeftPointCm(0.0f, 0.0f, 0.0f)
|
||||
, BackLowerRightPointCm(0.0f, 0.0f, 0.0f)
|
||||
{}
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the current state of the gaze tracker. Is designed in such a way as later values indicates a higher level of readiness to make it easy to use in interactions.
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class ETobiiGazeTrackerStatus : uint8
|
||||
{
|
||||
/** The gaze tracker is not connected to UE4 for some reason. The tracker might not be plugged in, the game window is currently running on a screen without a gaze tracker or is otherwise not available. */
|
||||
NotConnected,
|
||||
|
||||
/** The gaze tracker is connected, but cannot track as it is missing some important configuration step. */
|
||||
NotConfigured,
|
||||
|
||||
/** Gaze Tracking has been disabled by the user or developer or some other eventuality, like for example the game being minimized which might suspend the tracking. */
|
||||
Disabled,
|
||||
|
||||
/** The gaze tracker is running but has not yet detected a user. Not relevant in XR. */
|
||||
UserNotPresent,
|
||||
|
||||
/** The gaze tracker has detected a user and is actively tracking them. They appear not to be focusing on the game window at the moment however, so be careful about how you are using the data. */
|
||||
UserPresent
|
||||
};
|
||||
|
||||
/**
|
||||
* Some gaze trackers might only be able to deliver data for one eye, and others might be able to serve data for both.
|
||||
* If you are planning to use the left or right gaze data functions, checking that the gaze tracker you are working with supports them is recommended.
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class ETobiiGazeTrackerCapability : uint8
|
||||
{
|
||||
/** This gaze tracker can deliver combined gaze data. */
|
||||
None = 0 UMETA(DisplayName = "No Support"),
|
||||
|
||||
/** This gaze tracker can deliver combined gaze data. */
|
||||
CombinedGazeData = 1 UMETA(DisplayName = "Supports Combined Gaze Data"),
|
||||
|
||||
/** This gaze tracker can deliver individual gaze data for the left eye. */
|
||||
LeftGazeData = 2 UMETA(DisplayName = "Supports Left Eye Gaze Data"),
|
||||
|
||||
/** This gaze tracker can deliver individual gaze data for the right eye. */
|
||||
RightGazeData = 4 UMETA(DisplayName = "Supports Right Eye Gaze Data")
|
||||
};
|
||||
112
Source/TobiiCore/TobiiCore.Build.cs
Normal file
112
Source/TobiiCore/TobiiCore.Build.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2017- Tobii Technology AB. All rights reserved.
|
||||
*
|
||||
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
|
||||
******************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Diagnostics;
|
||||
using Tools.DotNETCommon;
|
||||
|
||||
namespace UnrealBuildTool.Rules
|
||||
{
|
||||
public class TobiiCore : ModuleRules
|
||||
{
|
||||
//If you want to remove eyetracking functionality from your builds without modifying your game code / project you can do so easily by setting this to false.
|
||||
private bool IsEyetrackingActive { get { return true; } }
|
||||
|
||||
public TobiiCore(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[]
|
||||
{
|
||||
"Core"
|
||||
, "CoreUObject"
|
||||
, "Engine"
|
||||
, "UMG"
|
||||
, "InputCore"
|
||||
, "InputDevice"
|
||||
, "HeadMountedDisplay"
|
||||
});
|
||||
|
||||
PublicDependencyModuleNames.AddRange(new string[]
|
||||
{
|
||||
"EyeTracker"
|
||||
});
|
||||
|
||||
if (Target.bBuildEditor)
|
||||
{
|
||||
DynamicallyLoadedModuleNames.AddRange(new string[] { "LevelEditor" });
|
||||
PublicDependencyModuleNames.AddRange(new string[]
|
||||
{
|
||||
"Slate"
|
||||
, "SlateCore"
|
||||
, "EditorStyle"
|
||||
, "UnrealEd"
|
||||
, "MainFrame"
|
||||
, "GameProjectGeneration"
|
||||
, "WebBrowser"
|
||||
, "RHI"
|
||||
});
|
||||
}
|
||||
|
||||
string AssemblyLocation = Path.GetDirectoryName(new Uri(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).LocalPath);
|
||||
DirectoryReference RootDirectory = new DirectoryReference(Path.Combine(AssemblyLocation, "..", "..", ".."));
|
||||
bool IsEnginePlugin = RootDirectory.GetDirectoryName() == "Engine";
|
||||
PublicDefinitions.Add("TOBII_COMPILE_AS_ENGINE_PLUGIN=" + (IsEnginePlugin ? 1 : 0));
|
||||
|
||||
//Platform specific
|
||||
if (IsEyetrackingActive && (Target.Platform == UnrealTargetPlatform.Win64 || Target.Platform == UnrealTargetPlatform.Win32))
|
||||
{
|
||||
string PlatformString = (Target.Platform == UnrealTargetPlatform.Win64) ? "Win64" : "Win32";
|
||||
string PluginsPath = Path.Combine(ModuleDirectory, "../../../");
|
||||
string TobiiRelativeAPIPath = "TobiiEyetracking/ThirdParty/GameIntegration";
|
||||
string TobiiRelativeIncludePath = Path.Combine(TobiiRelativeAPIPath, "include");
|
||||
string TobiiRelativeLibraryBasePath = Path.Combine(TobiiRelativeAPIPath, "lib");
|
||||
|
||||
//Includes
|
||||
PrivateIncludePaths.Add(Path.Combine(PluginsPath, TobiiRelativeIncludePath));
|
||||
|
||||
//Add libraries
|
||||
AddLibrary(Path.Combine(PluginsPath, TobiiRelativeLibraryBasePath, PlatformString, (Target.Platform == UnrealTargetPlatform.Win32) ? "tobii_gameintegration_x86.lib" : "tobii_gameintegration_x64.lib"));
|
||||
|
||||
//Add DLL
|
||||
string RelativeGICDllPath = "";
|
||||
string GICDllName = (Target.Platform == UnrealTargetPlatform.Win32) ? "tobii_gameintegration_x86.dll" : "tobii_gameintegration_x64.dll";
|
||||
|
||||
if (IsEnginePlugin)
|
||||
{
|
||||
RelativeGICDllPath = Path.Combine("Binaries/ThirdParty/TobiiEyetracking", PlatformString, GICDllName);
|
||||
RuntimeDependencies.Add("$(EngineDir)/" + RelativeGICDllPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
RelativeGICDllPath = Path.Combine(TobiiRelativeLibraryBasePath, PlatformString, GICDllName);
|
||||
RuntimeDependencies.Add(Path.Combine(PluginsPath, RelativeGICDllPath));
|
||||
}
|
||||
|
||||
PublicDefinitions.Add("TOBII_EYETRACKING_ACTIVE=1");
|
||||
PublicDefinitions.Add("TOBII_GIC_RELATIVE_DLL_PATH=R\"(" + RelativeGICDllPath + ")\"");
|
||||
PublicDelayLoadDLLs.Add(GICDllName);
|
||||
}
|
||||
else
|
||||
{
|
||||
PublicDefinitions.Add("TOBII_EYETRACKING_ACTIVE=0");
|
||||
}
|
||||
}
|
||||
|
||||
private void AddLibrary(string libraryPath)
|
||||
{
|
||||
if (File.Exists(libraryPath))
|
||||
{
|
||||
PublicAdditionalLibraries.Add(libraryPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine("Cannot find Tobii API Lib file. Path does not exist! " + libraryPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user