mirror of
https://github.com/litruv/TobiiEyetracking.git
synced 2026-07-24 18:56:09 +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
|
||||
Reference in New Issue
Block a user