Added Tobii from 4.23 + updated

This commit is contained in:
2023-03-04 04:19:09 +11:00
parent 2d5b9008e5
commit 1cde391516
521 changed files with 11735 additions and 0 deletions

View File

@@ -0,0 +1,161 @@
/******************************************************************************
* Copyright 2017- Tobii Technology AB. All rights reserved.
*
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
******************************************************************************/
#include "STobiiGazeFocusableWidget.h"
#include "TobiiGTOMBlueprintLibrary.h"
#include "Engine/Canvas.h"
#include "Engine/Engine.h"
#include "Components/WidgetComponent.h"
#include "HAL/IConsoleManager.h"
#include "Slate/SceneViewport.h"
static TAutoConsoleVariable<int32> CVarEnableCleanUI(TEXT("tobii.EnableCleanUI"), 1, TEXT("0 - CleanUI is disabled. 1 - CleanUI is enabled."));
static TAutoConsoleVariable<float> CVarCleanUIFadeInTimeSecs(TEXT("tobii.CleanUIFadeInTimeSecs"), 0.0f, TEXT("We want the fade in time to be fairly fast so that the information is instantly available to the user."));
static TAutoConsoleVariable<float> CVarCleanUIFadeOutTimeSecs(TEXT("tobii.CleanUIFadeOutTimeSecs"), 1.8f, TEXT("The fade out time should be fairly slow however to make sure that it doesn't draw peripheral vision attention."));
static TAutoConsoleVariable<float> CVarCleanUIMinAlpha(TEXT("tobii.CleanUIMinAlpha"), 0.3f, TEXT("The clean UI won't fade something out beyond this minimum alpha."));
static TAutoConsoleVariable<float> CVarCleanUIMaxAlpha(TEXT("tobii.CleanUIMaxAlpha"), 1.0f, TEXT("Change this if you don't want the clean UI to increase alpha beyond this point"));
STobiiGazeFocusableWidget::STobiiGazeFocusableWidget()
: CleanUIAlpha(1.0f)
, CleanUIContainersToPollHitsFrom()
{
bCanSupportFocus = false;
SetCanTick(true);
}
void STobiiGazeFocusableWidget::Construct(const FArguments& InArgs)
{
SBox::FArguments ParentArgs;
ParentArgs._HAlign = InArgs._HAlign;
ParentArgs._VAlign = InArgs._VAlign;
ParentArgs._Padding = InArgs._Padding;
ParentArgs._Content = InArgs._Content;
SBox::Construct(ParentArgs);
OnHovered = InArgs._OnHovered;
OnUnhovered = InArgs._OnUnhovered;
}
bool STobiiGazeFocusableWidget::HitByGaze()
{
return UMGWidget.IsValid() && (UMGWidget->CleanUIMode == ETobiiCleanUIMode::FocusExclusive ?
UMGWidget->HasFocus() : UMGWidget->IsInFocusCollection());
}
void STobiiGazeFocusableWidget::AddExtraCleanUIContainerToPollHitsFrom(STobiiGazeFocusableWidget* CleanUIContainerToPoll, bool PollGazeHits /*= true*/, bool PollMouseHits /*= true*/)
{
if (CleanUIContainerToPoll != nullptr)
{
CleanUIPollingInfo PollingInfo;
PollingInfo.CleanUIToPollFrom = TWeakPtr<SWidget>(CleanUIContainerToPoll->AsShared());
PollingInfo.PollGaze = PollGazeHits;
PollingInfo.PollPointer = PollMouseHits;
CleanUIContainersToPollHitsFrom.Add(PollingInfo);
}
}
void STobiiGazeFocusableWidget::RemoveExtraCleanUIContainerToPollHitsFrom(STobiiGazeFocusableWidget* CleanUIContainerToStopPolling)
{
if (CleanUIContainerToStopPolling != nullptr)
{
CleanUIContainersToPollHitsFrom.RemoveAll([=](CleanUIPollingInfo& CurCleanUIPollingInfo)
{
return !CurCleanUIPollingInfo.CleanUIToPollFrom.IsValid() || CurCleanUIPollingInfo.CleanUIToPollFrom.HasSameObject(CleanUIContainerToStopPolling);
});
}
}
void STobiiGazeFocusableWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
SWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
if (!UMGWidget.IsValid()
|| UMGWidget->CleanUIMode == ETobiiCleanUIMode::Disabled
|| GEngine == nullptr
|| GEngine->GameViewport == nullptr
|| GEngine->GameViewport->GetGameViewport() == nullptr
|| GetChildren()->Num() == 0)
{
CleanUIAlpha = 1.0f;
return;
}
bool bTriggerCleanUIOnMouseOver = UMGWidget.IsValid() && UMGWidget->bTriggerCleanUIOnMouseOver;
bool bHitByGaze = HitByGaze();
bool bShouldFadeIn = bHitByGaze || (bTriggerCleanUIOnMouseOver && bIsHovered);
// Check if we have any hits on our CleanUIContainersToPollHitsFrom to see if we should still fade in even though we still think we should fade out
if (!bShouldFadeIn)
{
for (CleanUIPollingInfo& PollingInfo : CleanUIContainersToPollHitsFrom)
{
if (PollingInfo.CleanUIToPollFrom.IsValid())
{
TSharedPtr<SWidget> CleanUIWidget = PollingInfo.CleanUIToPollFrom.Pin();
STobiiGazeFocusableWidget* CleanUIToPollFrom = (STobiiGazeFocusableWidget*)CleanUIWidget.Get();
if ((PollingInfo.PollGaze && CleanUIToPollFrom->HitByGaze())
|| PollingInfo.PollPointer && bTriggerCleanUIOnMouseOver && CleanUIToPollFrom->bIsHovered)
{
bShouldFadeIn = true;
break;
}
}
}
}
UMGWidget->TimeCleanUIIsSuppressedForSecs = FMath::Max(UMGWidget->TimeCleanUIIsSuppressedForSecs -= InDeltaTime, 0.0f);
bool bCleanUISuppressed = UMGWidget->TimeCleanUIIsSuppressedForSecs > FLT_EPSILON;
bShouldFadeIn = bShouldFadeIn || bCleanUISuppressed;
float MinAlpha = UMGWidget->CleanUIMinAlphaOverride >= 0.0f ? UMGWidget->CleanUIMinAlphaOverride : CVarCleanUIMinAlpha.GetValueOnAnyThread();
float MaxAlpha = UMGWidget->CleanUIMaxAlphaOverride >= 0.0f ? UMGWidget->CleanUIMaxAlphaOverride : CVarCleanUIMaxAlpha.GetValueOnAnyThread();
if (bShouldFadeIn)
{
float FadeInTime = UMGWidget->CleanUIFadeInTimeSecsOverride >= 0.0f ? UMGWidget->CleanUIFadeInTimeSecsOverride : CVarCleanUIFadeInTimeSecs.GetValueOnAnyThread();
CleanUIAlpha = FadeInTime > 0.0f ? FMath::Clamp(CleanUIAlpha + InDeltaTime / FadeInTime, MinAlpha, MaxAlpha) : MaxAlpha;
}
else
{
float FadeOutTime = UMGWidget->CleanUIFadeOutTimeSecsOverride >= 0.0f ? UMGWidget->CleanUIFadeOutTimeSecsOverride : CVarCleanUIFadeOutTimeSecs.GetValueOnAnyThread();
CleanUIAlpha = FadeOutTime > 0.0f ? FMath::Clamp(CleanUIAlpha - InDeltaTime / FadeOutTime, MinAlpha, MaxAlpha) : MinAlpha;
}
}
int32 STobiiGazeFocusableWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
const float Alpha = UMGWidget.IsValid() ? (UMGWidget->CleanUIMode == ETobiiCleanUIMode::Silent ? 1.0f : CleanUIAlpha) : 1.0f;
FWidgetStyle CompoundedWidgetStyle = FWidgetStyle(InWidgetStyle).BlendColorAndOpacityTint(FLinearColor(1.0f, 1.0f, 1.0f, Alpha));
return SBox::OnPaint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, CompoundedWidgetStyle, bParentEnabled);
}
void STobiiGazeFocusableWidget::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
SWidget::OnMouseEnter(MyGeometry, MouseEvent);
OnHovered.ExecuteIfBound();
Invalidate(EInvalidateWidget::Layout);
}
void STobiiGazeFocusableWidget::OnMouseLeave(const FPointerEvent& MouseEvent)
{
// Call parent implementation
SWidget::OnMouseLeave(MouseEvent);
OnUnhovered.ExecuteIfBound();
Invalidate(EInvalidateWidget::Layout);
}
STobiiGazeFocusableWidget* STobiiGazeFocusableWidget::TryCastToTobiiGazeFocusable(SWidget* Widget)
{
if (Widget != nullptr && Widget->GetType() == FName("STobiiGazeFocusableWidget"))
{
return (STobiiGazeFocusableWidget*)Widget;
}
return nullptr;
}

View File

@@ -0,0 +1,318 @@
/******************************************************************************
* Copyright 2017- Tobii Technology AB. All rights reserved.
*
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
******************************************************************************/
#include "STobiiRadialMenuWidget.h"
#include "TobiiGTOMBlueprintLibrary.h"
#include "Engine/Canvas.h"
#include "Engine/Engine.h"
#include "Components/WidgetComponent.h"
#include "Framework/Application/SlateApplication.h"
#include "Slate/SceneViewport.h"
#define TWO_PI (6.28318530718)
#define PI_OVER_TWO (1.57079632679)
STobiiRadialMenuWidget::STobiiRadialMenuWidget()
: Children(this)
{
SetCanTick(false);
bCanSupportFocus = false;
}
void STobiiRadialMenuWidget::Construct(const STobiiRadialMenuWidget::FArguments& InArgs)
{
const int32 NumSlots = InArgs.Slots.Num();
for (int32 SlotIndex = 0; SlotIndex < NumSlots; ++SlotIndex)
{
Children.Add(InArgs.Slots[SlotIndex]);
}
bUseHardBorder = InArgs._UseHardBorder;
BorderColor = InArgs._BorderColor;
PanelColor = InArgs._PanelColor;
BorderThicknessPx = InArgs._BorderThicknessPx;
SegmentSeparationPx = InArgs._SegmentSeparationPx;
AngularDisplacementDeg = InArgs._AngularDisplacementDeg;
RadiusPx = InArgs._RadiusPx;
VertexCount = InArgs._VertexCount;
BrushResourceHandle = FSlateApplication::Get().GetRenderer()->GetResourceHandle(*FCoreStyle::Get().GetDefaultBrush());
}
void STobiiRadialMenuWidget::ClearChildren()
{
if (Children.Num())
{
Invalidate(EInvalidateWidget::Layout);
Children.Empty();
}
}
int32 STobiiRadialMenuWidget::RemoveSlot(const TSharedRef<SWidget>& SlotWidget)
{
Invalidate(EInvalidateWidget::Layout);
for (int32 SlotIdx = 0; SlotIdx < Children.Num(); ++SlotIdx)
{
if (SlotWidget == Children[SlotIdx].GetWidget())
{
Children.RemoveAt(SlotIdx);
return SlotIdx;
}
}
return -1;
}
void STobiiRadialMenuWidget::GeneratePanelRenderData(const FGeometry& AllottedGeometry, int32 NrChildren, TArray<FTobiiRadialMenuPanelRenderData>& OutChildRenderData) const
{
OutChildRenderData.Empty(NrChildren);
const FVector2D WidgetCenter = AllottedGeometry.GetLocalSize() / 2.0f;
const int32 VerticesPerSegment = FMath::Max(VertexCount / Children.Num(), 3);
const float ActualRadius = FMath::Min(WidgetCenter.X, WidgetCenter.Y);
const float AngleStep = TWO_PI / Children.Num();
const float AngleHalfStep = AngleStep / 2.0f;
const float AngleMinorStep = AngleStep / VerticesPerSegment;
const float HalfSeparation = SegmentSeparationPx / 2.0f;
const float BorderCenterOffsetDist = HalfSeparation / FMath::Sin(AngleHalfStep);
const float BorderSeparationAngle = FMath::Asin(HalfSeparation / ActualRadius);
const float NoBorderDistOffset = HalfSeparation + BorderThicknessPx;
const float NoBorderRadius = ActualRadius - BorderThicknessPx;
const float NoBorderCenterOffsetDist = NoBorderDistOffset / FMath::Sin(AngleHalfStep);
const float NoBorderSeparationAngle = FMath::Asin(NoBorderDistOffset / NoBorderRadius);
float CurrentChildAngle = PI_OVER_TWO + FMath::DegreesToRadians(AngularDisplacementDeg); //Start at the top
for (int32 ChildIndex = 0; ChildIndex < NrChildren; ++ChildIndex)
{
FTobiiRadialMenuPanelRenderData NewRenderData;
const STobiiRadialMenuWidget::FSlot& CurChild = Children[ChildIndex];
const float ChildAlpha = CurChild.AlphaAttr.Get();
FColor AdjustedBorderColor = BorderColor;
AdjustedBorderColor.A *= ChildAlpha;
FColor AdjustedPanelColor = PanelColor;
AdjustedPanelColor.A *= ChildAlpha;
//Panel polygon
{
float ChildSine, ChildCosine;
FMath::SinCos(&ChildSine, &ChildCosine, -CurrentChildAngle);
FVector2D WidgetCenterToChildCenterDir(ChildCosine, ChildSine);
//First generate the nexus point
const FVector2D CenterVertexPosition = WidgetCenter + WidgetCenterToChildCenterDir * NoBorderCenterOffsetDist;
FSlateVertex CenterVertex = FSlateVertex::Make<ESlateVertexRounding::Enabled>(AllottedGeometry.GetAccumulatedRenderTransform(), CenterVertexPosition, FVector2D::ZeroVector, AdjustedPanelColor);
NewRenderData.PanelVertexData.Add(CenterVertex);
//Next we need to find our segment extreme points
const float SegmentStartVertexAngle = CurrentChildAngle - AngleHalfStep + NoBorderSeparationAngle;
const float SegmentEndVertexAngle = CurrentChildAngle + AngleHalfStep - NoBorderSeparationAngle;
//Generate all edge vertices
for (int32 VertexIdx = 0; VertexIdx < VerticesPerSegment; VertexIdx++)
{
const float AngleAlpha = (float)VertexIdx / (VerticesPerSegment - 1);
const float CurrentAngle = FMath::Lerp(SegmentStartVertexAngle, SegmentEndVertexAngle, AngleAlpha);
float VertexSine, VertexCosine;
FMath::SinCos(&VertexSine, &VertexCosine, -CurrentAngle);
const FVector2D SegmentVertexPosition = WidgetCenter + FVector2D(NoBorderRadius * VertexCosine, NoBorderRadius * VertexSine);
FSlateVertex SegmentVertex = FSlateVertex::Make<ESlateVertexRounding::Enabled>(AllottedGeometry.GetAccumulatedRenderTransform(), SegmentVertexPosition, FVector2D::ZeroVector, AdjustedPanelColor);
NewRenderData.PanelVertexData.Add(SegmentVertex);
}
//Build indices
for (int32 VertexIdx = 1; VertexIdx < VerticesPerSegment; VertexIdx++)
{
NewRenderData.PanelIndexData.Add(0);
NewRenderData.PanelIndexData.Add(VertexIdx);
NewRenderData.PanelIndexData.Add(VertexIdx + 1);
}
}
//Border polygon
{
float ChildSine, ChildCosine;
FMath::SinCos(&ChildSine, &ChildCosine, -CurrentChildAngle);
FVector2D WidgetCenterToChildCenterDir(ChildCosine, ChildSine);
//First generate the nexus point
const FVector2D CenterVertexPosition = WidgetCenter + WidgetCenterToChildCenterDir * BorderCenterOffsetDist;
FSlateVertex CenterVertex = FSlateVertex::Make<ESlateVertexRounding::Enabled>(AllottedGeometry.GetAccumulatedRenderTransform(), CenterVertexPosition, FVector2D::ZeroVector, AdjustedBorderColor);
NewRenderData.BorderVertexData.Add(CenterVertex);
//Next we need to find our segment extreme points
const float SegmentStartVertexAngle = CurrentChildAngle - AngleHalfStep + BorderSeparationAngle;
const float SegmentEndVertexAngle = CurrentChildAngle + AngleHalfStep - BorderSeparationAngle;
//Generate all edge vertices
for (int32 VertexIdx = 0; VertexIdx < VerticesPerSegment; VertexIdx++)
{
const float AngleAlpha = (float)VertexIdx / (VerticesPerSegment - 1);
const float CurrentAngle = FMath::Lerp(SegmentStartVertexAngle, SegmentEndVertexAngle, AngleAlpha);
float VertexSine, VertexCosine;
FMath::SinCos(&VertexSine, &VertexCosine, -CurrentAngle);
const FVector2D SegmentVertexPosition = WidgetCenter + FVector2D(ActualRadius * VertexCosine, ActualRadius * VertexSine);
FSlateVertex SegmentVertex = FSlateVertex::Make<ESlateVertexRounding::Enabled>(AllottedGeometry.GetAccumulatedRenderTransform(), SegmentVertexPosition, FVector2D::ZeroVector, AdjustedBorderColor);
NewRenderData.BorderVertexData.Add(SegmentVertex);
}
//Add the panel vertices at the end of the array
const int32 FirstPanelVertexIdx = NewRenderData.BorderVertexData.Num();
for (FSlateVertex SlateVertex : NewRenderData.PanelVertexData)
{
if (bUseHardBorder)
{
SlateVertex.Color = AdjustedBorderColor;
}
NewRenderData.BorderVertexData.Add(SlateVertex);
}
//Build indices, building quads from the inner panel outwards
for (int32 VertexIdx = 0; VertexIdx < VerticesPerSegment; VertexIdx++)
{
//First tri
NewRenderData.BorderIndexData.Add(FirstPanelVertexIdx + VertexIdx);
NewRenderData.BorderIndexData.Add(VertexIdx);
NewRenderData.BorderIndexData.Add(VertexIdx + 1);
//Second tri
NewRenderData.BorderIndexData.Add(FirstPanelVertexIdx + VertexIdx);
NewRenderData.BorderIndexData.Add(VertexIdx + 1);
NewRenderData.BorderIndexData.Add(FirstPanelVertexIdx + VertexIdx + 1);
}
//Final two tris
NewRenderData.BorderIndexData.Add(NewRenderData.BorderVertexData.Num() - 1);
NewRenderData.BorderIndexData.Add(FirstPanelVertexIdx - 1);
NewRenderData.BorderIndexData.Add(0);
NewRenderData.BorderIndexData.Add(NewRenderData.BorderVertexData.Num() - 1);
NewRenderData.BorderIndexData.Add(0);
NewRenderData.BorderIndexData.Add(FirstPanelVertexIdx);
}
OutChildRenderData.Add(MoveTemp(NewRenderData));
CurrentChildAngle += AngleStep;
}
}
void STobiiRadialMenuWidget::OnArrangeChildren(const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren) const
{
if (Children.Num() > 0)
{
const FVector2D WidgetCenter = AllottedGeometry.GetLocalSize() / 2.0f;
const float ActualRadius = FMath::Min(WidgetCenter.X, WidgetCenter.Y);
const float AngleStep = TWO_PI / Children.Num();
FVector2D LargestInscribedRect;
float DistanceToRectCenter;
UTobiiGTOMBlueprintLibrary::FindLargestInscribedAlignedRect(AngleStep, ActualRadius, LargestInscribedRect, DistanceToRectCenter);
const float ChildElementSideLength = FMath::Min(LargestInscribedRect.X, LargestInscribedRect.Y);
const float SlightlyScaledDownSize = ChildElementSideLength * 0.8f;
float CurrentAngle = PI_OVER_TWO + FMath::DegreesToRadians(AngularDisplacementDeg); //Start at the top
for (int32 ChildIndex = 0; ChildIndex < Children.Num(); ++ChildIndex)
{
const STobiiRadialMenuWidget::FSlot& CurChild = Children[ChildIndex];
const TSharedRef<SWidget>& CurWidget = CurChild.GetWidget();
const EVisibility ChildVisibility = CurWidget->GetVisibility();
if (ArrangedChildren.Accepts(ChildVisibility))
{
const FVector2D Offset = CurChild.OffsetAttr.Get();
float ChildSine, ChildCosine;
FMath::SinCos(&ChildSine, &ChildCosine, -CurrentAngle);
const FVector2D ChildCenter(DistanceToRectCenter * ChildCosine, DistanceToRectCenter * ChildSine);
const float ChildScale = CurChild.ScaleAttr.Get();
const FVector2D ChildSize(SlightlyScaledDownSize * ChildScale, SlightlyScaledDownSize * ChildScale);
const FVector2D BaseChildOffset = -ChildSize / 2.0f;
const FVector2D LocalPosition = WidgetCenter + ChildCenter + BaseChildOffset + Offset;
CurrentAngle += AngleStep;
ArrangedChildren.AddWidget(ChildVisibility, AllottedGeometry.MakeChild(CurWidget, LocalPosition, ChildSize));
}
}
}
}
int32 STobiiRadialMenuWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
SCOPED_NAMED_EVENT_TEXT("SConstraintCanvas", FColor::Orange);
if (Children.Num() < 2)
{
return LayerId;
}
FArrangedChildren ArrangedChildren(EVisibility::Visible);
OnArrangeChildren(AllottedGeometry, ArrangedChildren);
TArray<FTobiiRadialMenuPanelRenderData> ChildRenderData;
GeneratePanelRenderData(AllottedGeometry, Children.Num(), ChildRenderData);
const bool bForwardedEnabled = ShouldBeEnabled(bParentEnabled);
const float AngleStep = TWO_PI / Children.Num();
float CurrentChildAngle = PI_OVER_TWO + FMath::DegreesToRadians(AngularDisplacementDeg); //Start at the top
// Because we paint multiple children, we must track the maximum layer id that they produced in case one of our parents
// wants to an overlay for all of its contents.
int32 MaxLayerId = LayerId;
const FPaintArgs NewArgs = Args.WithNewParent(this);
for (int32 ChildIndex = 0; ChildIndex < ArrangedChildren.Num(); ++ChildIndex)
{
FArrangedWidget& CurWidget = ArrangedChildren[ChildIndex];
if (!IsChildWidgetCulled(MyCullingRect, CurWidget))
{
MaxLayerId++;
//Draw underlying panel if our render data is valid
if (ChildIndex < ChildRenderData.Num())
{
const FTobiiRadialMenuPanelRenderData& CurrentRenderData = ChildRenderData[ChildIndex];
FSlateDrawElement::MakeCustomVerts(OutDrawElements, LayerId
, BrushResourceHandle
, CurrentRenderData.PanelVertexData
, CurrentRenderData.PanelIndexData
, nullptr, 0, 0, ESlateDrawEffect::NoPixelSnapping);
FSlateDrawElement::MakeCustomVerts(OutDrawElements, LayerId
, BrushResourceHandle
, CurrentRenderData.BorderVertexData
, CurrentRenderData.BorderIndexData
, nullptr, 0, 0, ESlateDrawEffect::NoPixelSnapping);
}
const STobiiRadialMenuWidget::FSlot& CurChild = Children[ChildIndex];
FWidgetStyle CompoundedWidgetStyle = FWidgetStyle(InWidgetStyle).BlendColorAndOpacityTint(FLinearColor(1.0f, 1.0f, 1.0f, CurChild.AlphaAttr.Get()));
CurWidget.Widget->Paint(NewArgs, CurWidget.Geometry, MyCullingRect, OutDrawElements, MaxLayerId, CompoundedWidgetStyle, bForwardedEnabled);
CurrentChildAngle += AngleStep;
}
}
return MaxLayerId;
}
FVector2D STobiiRadialMenuWidget::ComputeDesiredSize(float LayoutScaleMultiplier) const
{
const float DiameterPx = RadiusPx * 2.0f;
return FVector2D(DiameterPx, DiameterPx);
}
FChildren* STobiiRadialMenuWidget::GetChildren()
{
return &Children;
}

View File

@@ -0,0 +1,223 @@
/******************************************************************************
* Copyright 2017- Tobii Technology AB. All rights reserved.
*
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
******************************************************************************/
#include "TobiiGazeFocusableWidget.h"
#include "TobiiGTOMBlueprintLibrary.h"
#include "Components/SizeBoxSlot.h"
#define LOCTEXT_NAMESPACE "UMG"
static TMap<FTobiiFocusableUID, TWeakObjectPtr<UTobiiGazeFocusableWidget>> GRegisteredTobiiScreenSpaceFocusableWidgets;
UTobiiGazeFocusableWidget::UTobiiGazeFocusableWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, OnHovered()
, OnUnhovered()
, bHasGazeFocus(false)
, bIsGazeFocusable(true)
, GazeFocusPriority(0.0f)
, MaxFocusDistance(-1.0f)
, FocusLayer("")
, ReceivedGazeFocus()
, LostGazeFocus()
, CleanUIMode(ETobiiCleanUIMode::Disabled)
, TimeCleanUIIsSuppressedForSecs(0.0f)
, CleanUIFadeInTimeSecsOverride(-1.0f)
, CleanUIFadeOutTimeSecsOverride(-1.0f)
, CleanUIMinAlphaOverride(-1.0f)
, CleanUIMaxAlphaOverride(-1.0f)
, bTriggerCleanUIOnMouseOver(false)
, WorldSpaceHostWidgetComponent()
, MySlateWidget()
{
Visibility = ESlateVisibility::Visible;
bIsVariable = true;
}
void UTobiiGazeFocusableWidget::ReleaseSlateResources(bool bReleaseChildren)
{
Super::ReleaseSlateResources(bReleaseChildren);
MySlateWidget.Reset();
}
TSharedRef<SWidget> UTobiiGazeFocusableWidget::RebuildWidget()
{
MySizeBox = MySlateWidget = SNew(STobiiGazeFocusableWidget)
.OnHovered_UObject(this, &ThisClass::SlateHandleHovered)
.OnUnhovered_UObject(this, &ThisClass::SlateHandleUnhovered);
MySlateWidget->UMGWidget = this;
if (GetChildrenCount() > 0)
{
Cast<USizeBoxSlot>(GetContentSlot())->BuildSlot(MySizeBox.ToSharedRef());
}
return MySizeBox.ToSharedRef();
}
bool UTobiiGazeFocusableWidget::IsHoveredByPointer()
{
if (MySlateWidget.IsValid())
{
return MySlateWidget->IsHoveredByPointer();
}
return false;
}
void UTobiiGazeFocusableWidget::RegisterWidgetToGTOM(UWidgetComponent* HostWidget)
{
if (HostWidget != nullptr)
{
WorldSpaceHostWidgetComponent = HostWidget;
}
else
{
uint32 Uid = GetUniqueID();
if (GRegisteredTobiiScreenSpaceFocusableWidgets.Contains(Uid))
{
//Overwrite
GRegisteredTobiiScreenSpaceFocusableWidgets[Uid] = this;
}
else
{
GRegisteredTobiiScreenSpaceFocusableWidgets.Add(Uid, this);
}
}
}
bool UTobiiGazeFocusableWidget::IsWorldSpaceWidget()
{
return WorldSpaceHostWidgetComponent.IsValid();
}
UWidgetComponent* UTobiiGazeFocusableWidget::GetWorldSpaceHostWidgetComponent()
{
return WorldSpaceHostWidgetComponent.IsValid() ? WorldSpaceHostWidgetComponent.Get() : nullptr;
}
bool UTobiiGazeFocusableWidget::HasFocus()
{
FTobiiGazeFocusData FocusData;
return UTobiiGTOMBlueprintLibrary::GetGazeFocusData(FocusData) && FocusData.FocusedWidget == this;
}
bool UTobiiGazeFocusableWidget::IsInFocusCollection()
{
TArray<FTobiiGazeFocusData> AllFocusData;
if (UTobiiGTOMBlueprintLibrary::GetAllGazeFocusData(AllFocusData))
{
for (const FTobiiGazeFocusData& FocusData : AllFocusData)
{
if (FocusData.FocusedWidget == this)
{
return true;
}
}
}
return false;
}
float UTobiiGazeFocusableWidget::GetCleanUIAlpha()
{
if (MySlateWidget.IsValid())
{
return MySlateWidget->GetCleanUIAlpha();
}
return 1.0f;
}
void UTobiiGazeFocusableWidget::AddGazeFocusableWidgetToPollHitsFrom(UTobiiGazeFocusableWidget* GazeFocusableWidgetToPoll, bool PollGazeHits /*= true*/, bool PollMouseHits /*= true*/)
{
if (GazeFocusableWidgetToPoll != nullptr)
{
STobiiGazeFocusableWidget* SlateCleanUIContainerToPoll = GazeFocusableWidgetToPoll->GetSlateGazeFocusableWidget();
if (SlateCleanUIContainerToPoll != nullptr && MySlateWidget.IsValid())
{
MySlateWidget->AddExtraCleanUIContainerToPollHitsFrom(SlateCleanUIContainerToPoll, PollGazeHits, PollMouseHits);
}
}
}
void UTobiiGazeFocusableWidget::RemoveGazeFocusableWidgetToPollHitsFrom(UTobiiGazeFocusableWidget* GazeFocusableWidgetToStopPolling)
{
if (GazeFocusableWidgetToStopPolling != nullptr)
{
STobiiGazeFocusableWidget* SlateCleanUIContainerToStopPolling = GazeFocusableWidgetToStopPolling->GetSlateGazeFocusableWidget();
if (SlateCleanUIContainerToStopPolling != nullptr && MySlateWidget.IsValid())
{
MySlateWidget->RemoveExtraCleanUIContainerToPollHitsFrom(SlateCleanUIContainerToStopPolling);
}
}
}
void UTobiiGazeFocusableWidget::ReceiveFocus()
{
ReceivedGazeFocus.Broadcast(this);
}
void UTobiiGazeFocusableWidget::LoseFocus()
{
LostGazeFocus.Broadcast(this);
}
void UTobiiGazeFocusableWidget::SlateHandleHovered()
{
OnHovered.Broadcast(this);
}
void UTobiiGazeFocusableWidget::SlateHandleUnhovered()
{
OnUnhovered.Broadcast(this);
}
STobiiGazeFocusableWidget* UTobiiGazeFocusableWidget::GetSlateGazeFocusableWidget()
{
if (MySlateWidget.IsValid())
{
return MySlateWidget.Get();
}
return nullptr;
}
TMap<FTobiiFocusableUID, TWeakObjectPtr<UTobiiGazeFocusableWidget>>& UTobiiGazeFocusableWidget::GetTobiiScreenSpaceFocusableWidgets()
{
TArray<FTobiiFocusableUID> IdsToPrune;
for (auto& WidgetPair : GRegisteredTobiiScreenSpaceFocusableWidgets)
{
if (!WidgetPair.Value.IsValid())
{
IdsToPrune.Add(WidgetPair.Key);
}
}
for (FTobiiFocusableUID IdToPrune : IdsToPrune)
{
GRegisteredTobiiScreenSpaceFocusableWidgets.Remove(IdToPrune);
}
return GRegisteredTobiiScreenSpaceFocusableWidgets;
}
#if WITH_EDITOR
const FText UTobiiGazeFocusableWidget::GetPaletteCategory()
{
return LOCTEXT("Tobii", "Tobii");
}
#endif
///////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,334 @@
/******************************************************************************
* Copyright 2017- Tobii Technology AB. All rights reserved.
*
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
******************************************************************************/
#include "TobiiRadialMenuWidget.h"
#include "TobiiGazeFocusableWidget.h"
#include "STobiiGazeFocusableWidget.h"
#define LOCTEXT_NAMESPACE "UMG"
UTobiiRadialMenuSlot::UTobiiRadialMenuSlot(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, Offset(0.0f, 0.0f)
, Scale(1.0f)
, Alpha(1.0f)
, Slot(nullptr)
{
}
void UTobiiRadialMenuSlot::ReleaseSlateResources(bool bReleaseChildren)
{
Super::ReleaseSlateResources(bReleaseChildren);
Slot = nullptr;
}
void UTobiiRadialMenuSlot::BuildSlot(TSharedRef<STobiiRadialMenuWidget> RadialMenu)
{
Slot = &RadialMenu->AddSlot()
[
Content == nullptr ? SNullWidget::NullWidget : Content->TakeWidget()
];
SynchronizeProperties();
}
#if WITH_EDITOR
bool UTobiiRadialMenuSlot::NudgeByDesigner(const FVector2D& NudgeDirection, const TOptional<int32>& GridSnapSize)
{
return false;
}
bool UTobiiRadialMenuSlot::DragDropPreviewByDesigner(const FVector2D& LocalCursorPosition, const TOptional<int32>& XGridSnapSize, const TOptional<int32>& YGridSnapSize)
{
return false;
}
void UTobiiRadialMenuSlot::SynchronizeFromTemplate(const UPanelSlot* const TemplateSlot)
{
}
#endif //WITH_EDITOR
void UTobiiRadialMenuSlot::SetOffset(FVector2D InOffset)
{
Offset = InOffset;
if (Slot)
{
Slot->Offset(InOffset);
}
}
FVector2D UTobiiRadialMenuSlot::GetOffset() const
{
if (Slot)
{
return Slot->OffsetAttr.Get();
}
return FVector2D::ZeroVector;
}
void UTobiiRadialMenuSlot::SetScale(float InScale)
{
Scale = InScale;
if (Slot)
{
Slot->Scale(InScale);
}
}
float UTobiiRadialMenuSlot::GetScale() const
{
if (Slot)
{
return Slot->ScaleAttr.Get();
}
return 1.0f;
}
void UTobiiRadialMenuSlot::SetAlpha(float InAlpha)
{
Alpha = InAlpha;
if (Slot)
{
Slot->Alpha(InAlpha);
}
}
float UTobiiRadialMenuSlot::GetAlpha() const
{
if (Slot)
{
return Slot->AlphaAttr.Get();
}
return 1.0f;
}
void UTobiiRadialMenuSlot::SynchronizeProperties()
{
SetOffset(Offset);
SetScale(Scale);
SetAlpha(Alpha);
}
#if WITH_EDITOR
void UTobiiRadialMenuSlot::PreEditChange(class FEditPropertyChain& PropertyAboutToChange)
{
Super::PreEditChange(PropertyAboutToChange);
}
void UTobiiRadialMenuSlot::PostEditChangeChainProperty(struct FPropertyChangedChainEvent& PropertyChangedEvent)
{
SynchronizeProperties();
Super::PostEditChangeProperty(PropertyChangedEvent);
}
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////////////
UTobiiRadialMenuWidget::UTobiiRadialMenuWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, bUseHardBorder(true)
, BorderColor(FColor(230, 230, 230))
, PanelColor(FColor(70, 70, 70))
, BorderThicknessPx(5.0f)
, SegmentSeparationPx(10.0f)
, AngularDisplacementDeg(0.0f)
, RadiusPx(500.0f)
, VertexCount(360)
{
bIsVariable = false;
STobiiRadialMenuWidget::FArguments Defaults;
Visibility = UWidget::ConvertRuntimeToSerializedVisibility(Defaults._Visibility.Get());
}
void UTobiiRadialMenuWidget::ReleaseSlateResources(bool bReleaseChildren)
{
Super::ReleaseSlateResources(bReleaseChildren);
MyRadialMenu.Reset();
}
TSharedRef<SWidget> UTobiiRadialMenuWidget::RebuildWidget()
{
MyRadialMenu = SNew(STobiiRadialMenuWidget)
.UseHardBorder(bUseHardBorder)
.BorderColor(BorderColor)
.PanelColor(PanelColor)
.BorderThicknessPx(BorderThicknessPx)
.SegmentSeparationPx(SegmentSeparationPx)
.AngularDisplacementDeg(AngularDisplacementDeg)
.RadiusPx(RadiusPx)
.VertexCount(VertexCount);
for (UPanelSlot* PanelSlot : Slots)
{
if (UTobiiRadialMenuSlot* TypedSlot = Cast<UTobiiRadialMenuSlot>(PanelSlot))
{
TypedSlot->Parent = this;
TypedSlot->BuildSlot(MyRadialMenu.ToSharedRef());
UTobiiGazeFocusableWidget* GazeFocusableWidget = Cast<UTobiiGazeFocusableWidget>(TypedSlot->Content);
if (GazeFocusableWidget != nullptr)
{
STobiiGazeFocusableWidget* SlateWidget = GazeFocusableWidget->GetSlateGazeFocusableWidget();
//SlateWidget->ParentRadialMenuSlot = TypedSlot->GetSlateSlot();
}
}
}
return MyRadialMenu.ToSharedRef();
}
UClass* UTobiiRadialMenuWidget::GetSlotClass() const
{
return UTobiiRadialMenuSlot::StaticClass();
}
void UTobiiRadialMenuWidget::SetUseHardBorder(bool bNewUseHardBorder)
{
bUseHardBorder = bNewUseHardBorder;
if (MyRadialMenu.IsValid())
{
MyRadialMenu->bUseHardBorder = bUseHardBorder;
}
}
void UTobiiRadialMenuWidget::SetBorderColor(FColor NewBorderColor)
{
BorderColor = NewBorderColor;
if (MyRadialMenu.IsValid())
{
MyRadialMenu->BorderColor = BorderColor;
}
}
void UTobiiRadialMenuWidget::SetPanelColor(FColor NewPanelColor)
{
PanelColor = NewPanelColor;
if (MyRadialMenu.IsValid())
{
MyRadialMenu->PanelColor = PanelColor;
}
}
void UTobiiRadialMenuWidget::SetBorderThicknessPx(float NewBorderThicknessPx)
{
BorderThicknessPx = NewBorderThicknessPx;
if (MyRadialMenu.IsValid())
{
MyRadialMenu->BorderThicknessPx = BorderThicknessPx;
}
}
void UTobiiRadialMenuWidget::SetSegmentSeparationPx(float NewSegmentSeparationPx)
{
SegmentSeparationPx = NewSegmentSeparationPx;
if (MyRadialMenu.IsValid())
{
MyRadialMenu->SegmentSeparationPx = SegmentSeparationPx;
}
}
void UTobiiRadialMenuWidget::SetAngularDisplacementDeg(float NewAngularDisplacementDeg)
{
AngularDisplacementDeg = NewAngularDisplacementDeg;
if (MyRadialMenu.IsValid())
{
MyRadialMenu->AngularDisplacementDeg = AngularDisplacementDeg;
}
}
void UTobiiRadialMenuWidget::SetRadiusPx(float NewRadiusPx)
{
RadiusPx = NewRadiusPx;
if (MyRadialMenu.IsValid())
{
MyRadialMenu->RadiusPx = RadiusPx;
}
}
void UTobiiRadialMenuWidget::SetVertexCount(int32 NewVertexCount)
{
VertexCount = NewVertexCount;
if (MyRadialMenu.IsValid())
{
MyRadialMenu->VertexCount = VertexCount;
}
}
void UTobiiRadialMenuWidget::OnSlotAdded(UPanelSlot* InSlot)
{
// Add the child to the live canvas if it already exists
if (MyRadialMenu.IsValid())
{
CastChecked<UTobiiRadialMenuSlot>(InSlot)->BuildSlot(MyRadialMenu.ToSharedRef());
}
}
void UTobiiRadialMenuWidget::OnSlotRemoved(UPanelSlot* InSlot)
{
// Remove the widget from the live slot if it exists.
if (MyRadialMenu.IsValid())
{
TSharedPtr<SWidget> Widget = InSlot->Content->GetCachedWidget();
if (Widget.IsValid())
{
MyRadialMenu->RemoveSlot(Widget.ToSharedRef());
}
}
}
UTobiiRadialMenuSlot* UTobiiRadialMenuWidget::AddRadialMenuChild(UWidget* Content)
{
return Cast<UTobiiRadialMenuSlot>(Super::AddChild(Content));
}
bool UTobiiRadialMenuWidget::GetGeometryForSlot(int32 SlotIndex, FGeometry& ArrangedGeometry) const
{
UTobiiRadialMenuSlot* PanelSlot = CastChecked<UTobiiRadialMenuSlot>(Slots[SlotIndex]);
return GetGeometryForSlot(PanelSlot, ArrangedGeometry);
}
bool UTobiiRadialMenuWidget::GetGeometryForSlot(UTobiiRadialMenuSlot* InSlot, FGeometry& ArrangedGeometry) const
{
if (InSlot->Content == nullptr)
{
return false;
}
if (MyRadialMenu.IsValid())
{
FArrangedChildren ArrangedChildren(EVisibility::All);
MyRadialMenu->ArrangeChildren(MyRadialMenu->GetCachedGeometry(), ArrangedChildren);
for (int32 ChildIndex = 0; ChildIndex < ArrangedChildren.Num(); ChildIndex++)
{
if (ArrangedChildren[ChildIndex].Widget == InSlot->Content->GetCachedWidget())
{
ArrangedGeometry = ArrangedChildren[ChildIndex].Geometry;
return true;
}
}
}
return false;
}
#if WITH_EDITOR
const FText UTobiiRadialMenuWidget::GetPaletteCategory()
{
return LOCTEXT("Tobii", "Tobii");
}
#endif
/////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,508 @@
/******************************************************************************
* Copyright 2017- Tobii Technology AB. All rights reserved.
*
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
******************************************************************************/
#include "TobiiGTOMBlueprintLibrary.h"
#include "TobiiGTOMModule.h"
#include "TobiiGazeFocusableComponent.h"
#include "TobiiGTOMInternalTypes.h"
#include "TobiiGTOMEngine.h"
#include "IEyeTracker.h"
#include "Engine/Engine.h"
#include "Engine/LocalPlayer.h"
#include "GameFramework/PlayerController.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/Paths.h"
#include "Slate/SceneViewport.h"
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define TWO_PI (6.28318530718)
UTobiiGTOMBlueprintLibrary::UTobiiGTOMBlueprintLibrary(const class FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void UTobiiGTOMBlueprintLibrary::SetGTOMPlayerController(APlayerController* NewGTOMPlayerController)
{
if (FTobiiGTOMModule::IsAvailable())
{
if (FTobiiGTOMModule::Get().GTOMInputDevice.IsValid())
{
FTobiiGTOMModule::Get().GTOMInputDevice->GTOMPlayerController = NewGTOMPlayerController;
}
if (GEngine && GEngine->EyeTrackingDevice.IsValid())
{
GEngine->EyeTrackingDevice->SetEyeTrackedPlayer(NewGTOMPlayerController);
}
}
}
bool UTobiiGTOMBlueprintLibrary::GetGazeFocusData(FTobiiGazeFocusData& OutFocusData)
{
if (FTobiiGTOMModule::IsAvailable() && FTobiiGTOMModule::Get().GTOMInputDevice.IsValid())
{
const TArray<FTobiiGazeFocusData>& FocusData = FTobiiGTOMModule::Get().GTOMInputDevice->GetFocusData();
if (FocusData.Num() > 0)
{
OutFocusData = FocusData[0];
return true;
}
}
OutFocusData.FocusedActor = nullptr;
OutFocusData.FocusedPrimitiveComponent = nullptr;
OutFocusData.FocusedWidget = nullptr;
return false;
}
bool UTobiiGTOMBlueprintLibrary::GetAllGazeFocusData(TArray<FTobiiGazeFocusData>& OutFocusData)
{
OutFocusData.Empty();
if (FTobiiGTOMModule::IsAvailable() && FTobiiGTOMModule::Get().GTOMInputDevice.IsValid())
{
OutFocusData = FTobiiGTOMModule::Get().GTOMInputDevice->GetFocusData();
return OutFocusData.Num() > 0;
}
return false;
}
bool UTobiiGTOMBlueprintLibrary::GetFilteredGazeFocusData(const TArray<FName>& FocusLayerFilterList, const bool bIsWhiteList, const bool bWantPrimitives, const bool bWantWidgets, FTobiiGazeFocusData& OutFocusData)
{
if (FTobiiGTOMModule::IsAvailable() && FTobiiGTOMModule::Get().GTOMInputDevice.IsValid())
{
const TArray<FTobiiGazeFocusData>& AllFocusData = FTobiiGTOMModule::Get().GTOMInputDevice->GetFocusData();
for (const FTobiiGazeFocusData& FocusData : AllFocusData)
{
if (bWantWidgets && FocusData.FocusedWidget.IsValid())
{
if (FTobiiGTOMUtils::ValidateFocusLayers(FocusLayerFilterList, bIsWhiteList, FocusData.FocusedWidget.Get()))
{
OutFocusData = FocusData;
return true;
}
}
else if(bWantPrimitives && FocusData.FocusedPrimitiveComponent.IsValid() && !FocusData.FocusedWidget.IsValid())
{
if (FTobiiGTOMUtils::ValidateFocusLayers(FocusLayerFilterList, bIsWhiteList, FocusData.FocusedPrimitiveComponent.Get()))
{
OutFocusData = FocusData;
return true;
}
}
}
}
OutFocusData.FocusedActor = nullptr;
OutFocusData.FocusedPrimitiveComponent = nullptr;
OutFocusData.FocusedWidget = nullptr;
return false;
}
bool UTobiiGTOMBlueprintLibrary::GetAllFilteredGazeFocusData(const TArray<FName>& FocusLayerFilterList, const bool bIsWhiteList, const bool bWantPrimitives, const bool bWantWidgets, TArray<FTobiiGazeFocusData>& OutFocusData)
{
OutFocusData.Empty();
if (FTobiiGTOMModule::IsAvailable() && FTobiiGTOMModule::Get().GTOMInputDevice.IsValid())
{
const TArray<FTobiiGazeFocusData>& AllFocusData = FTobiiGTOMModule::Get().GTOMInputDevice->GetFocusData();
for (const FTobiiGazeFocusData& FocusData : AllFocusData)
{
if (bWantWidgets && FocusData.FocusedWidget.IsValid())
{
if (FTobiiGTOMUtils::ValidateFocusLayers(FocusLayerFilterList, bIsWhiteList, FocusData.FocusedWidget.Get()))
{
OutFocusData.Add(FocusData);
}
}
else if (bWantPrimitives && FocusData.FocusedPrimitiveComponent.IsValid() && !FocusData.FocusedWidget.IsValid())
{
if (FTobiiGTOMUtils::ValidateFocusLayers(FocusLayerFilterList, bIsWhiteList, FocusData.FocusedPrimitiveComponent.Get()))
{
OutFocusData.Add(FocusData);
}
}
}
}
return OutFocusData.Num() > 0;
}
void UTobiiGTOMBlueprintLibrary::RegisterScreenSpaceGazeFocusableWidgets(UWidget* Root)
{
UUserWidget* UserWidget = Cast<UUserWidget>(Root);
if (UserWidget != nullptr)
{
RegisterScreenSpaceGazeFocusableWidgets(UserWidget->GetRootWidget());
}
else
{
UTobiiGazeFocusableWidget* GazeFocusableWidget = Cast<UTobiiGazeFocusableWidget>(Root);
if (GazeFocusableWidget != nullptr)
{
GazeFocusableWidget->RegisterWidgetToGTOM(nullptr);
}
UPanelWidget* Panel = Cast<UPanelWidget>(Root);
if (Panel != nullptr)
{
for (int32 ChildIdx = 0; ChildIdx < Panel->GetChildrenCount(); ChildIdx++)
{
RegisterScreenSpaceGazeFocusableWidgets(Panel->GetChildAt(ChildIdx));
}
}
}
}
const FHitResult& UTobiiGTOMBlueprintLibrary::GetNaiveGazeHit()
{
static FHitResult Dummy;
if (FTobiiGTOMModule::IsAvailable() && FTobiiGTOMModule::Get().GTOMInputDevice.IsValid())
{
return FTobiiGTOMModule::Get().GTOMInputDevice->CombinedWorldGazeHitData;
}
else
{
return Dummy;
}
}
bool UTobiiGTOMBlueprintLibrary::IsPrimitiveComponentGazeFocusable(UPrimitiveComponent* PrimitiveComponent)
{
return UTobiiGazeFocusableComponent::IsPrimitiveFocusable(PrimitiveComponent);
}
bool UTobiiGTOMBlueprintLibrary::IsWidgetGazeFocusable(UWidget* Widget)
{
return UTobiiGazeFocusableComponent::IsWidgetFocusable(Cast<UTobiiGazeFocusableWidget>(Widget));
}
bool UTobiiGTOMBlueprintLibrary::GetPrimitiveComponentFocusOffset(USceneComponent* Component, FVector& OutFocusOffset)
{
OutFocusOffset = FVector::ZeroVector;
if (Component == nullptr)
{
return false;
}
for (const FName& CurrentTag : Component->ComponentTags)
{
FString CurrentTagString = CurrentTag.ToString();
if (CurrentTagString.StartsWith(FTobiiPrimitiveComponentGazeFocusTags::PrimitiveFocusOffsetXTag))
{
FString Argument = CurrentTagString.RightChop(FTobiiPrimitiveComponentGazeFocusTags::PrimitiveFocusOffsetXTag.Len());
OutFocusOffset.X += FCString::Atof(*Argument);
}
if (CurrentTagString.StartsWith(FTobiiPrimitiveComponentGazeFocusTags::PrimitiveFocusOffsetYTag))
{
FString Argument = CurrentTagString.RightChop(FTobiiPrimitiveComponentGazeFocusTags::PrimitiveFocusOffsetYTag.Len());
OutFocusOffset.Y += FCString::Atof(*Argument);
}
if (CurrentTagString.StartsWith(FTobiiPrimitiveComponentGazeFocusTags::PrimitiveFocusOffsetZTag))
{
FString Argument = CurrentTagString.RightChop(FTobiiPrimitiveComponentGazeFocusTags::PrimitiveFocusOffsetZTag.Len());
OutFocusOffset.Z += FCString::Atof(*Argument);
}
}
return true;
}
bool UTobiiGTOMBlueprintLibrary::GetPrimitiveComponentFocusLocation(USceneComponent* Component, FVector& OutFocusLocation)
{
OutFocusLocation = FVector::ZeroVector;
if (Component == nullptr)
{
return false;
}
OutFocusLocation = Component->GetComponentLocation();
FVector Offset = FVector::ZeroVector;
for (const FName& CurrentTag : Component->ComponentTags)
{
FString CurrentTagString = CurrentTag.ToString();
if (CurrentTagString.StartsWith(FTobiiPrimitiveComponentGazeFocusTags::PrimitiveFocusOffsetXTag))
{
FString Argument = CurrentTagString.RightChop(FTobiiPrimitiveComponentGazeFocusTags::PrimitiveFocusOffsetXTag.Len());
Offset.X += FCString::Atof(*Argument);
}
if (CurrentTagString.StartsWith(FTobiiPrimitiveComponentGazeFocusTags::PrimitiveFocusOffsetYTag))
{
FString Argument = CurrentTagString.RightChop(FTobiiPrimitiveComponentGazeFocusTags::PrimitiveFocusOffsetYTag.Len());
Offset.Y += FCString::Atof(*Argument);
}
if (CurrentTagString.StartsWith(FTobiiPrimitiveComponentGazeFocusTags::PrimitiveFocusOffsetZTag))
{
FString Argument = CurrentTagString.RightChop(FTobiiPrimitiveComponentGazeFocusTags::PrimitiveFocusOffsetZTag.Len());
Offset.Z += FCString::Atof(*Argument);
}
}
OutFocusLocation += Component->GetComponentTransform().TransformVector(Offset);
return true;
}
bool UTobiiGTOMBlueprintLibrary::MakeGazeFocusDataForWidgetInteractionComponent(UWidgetInteractionComponent* WidgetInteraction, ECollisionChannel CollisionChannel, FTobiiGazeFocusData& OutGazeFocusData)
{
OutGazeFocusData = FTobiiGazeFocusData();
if (WidgetInteraction == nullptr || !WidgetInteraction->bEnableHitTesting)
{
return false;
}
UWorld* CurrentWorld = WidgetInteraction->GetWorld();
if (CurrentWorld == nullptr)
{
return false;
}
FHitResult HitResult;
FVector StartPoint = WidgetInteraction->GetComponentLocation();
FVector EndPoint = StartPoint + WidgetInteraction->GetForwardVector() * WidgetInteraction->InteractionDistance;
FCollisionQueryParams CollisionParams;
CollisionParams.AddIgnoredActor(WidgetInteraction->GetOwner());
if (CurrentWorld->LineTraceSingleByChannel(HitResult, StartPoint, EndPoint, CollisionChannel, CollisionParams))
{
UPrimitiveComponent* HitComponent = HitResult.GetComponent();
if (UTobiiGTOMBlueprintLibrary::IsPrimitiveComponentGazeFocusable(HitComponent))
{
OutGazeFocusData.FocusedActor = HitResult.GetActor();
OutGazeFocusData.FocusedPrimitiveComponent = HitComponent;
OutGazeFocusData.LastVisibleWorldLocation = HitResult.Location;
OutGazeFocusData.FocusConfidence = 1.0f;
for (const TWeakPtr<SWidget>& WidgetMaybe : WidgetInteraction->GetHoveredWidgetPath().Widgets)
{
if (WidgetMaybe.IsValid())
{
SWidget* HoveredWidgetPtr = WidgetMaybe.Pin().Get();
STobiiGazeFocusableWidget* TobiiSlateWidget = STobiiGazeFocusableWidget::TryCastToTobiiGazeFocusable(HoveredWidgetPtr);
if (TobiiSlateWidget != nullptr && TobiiSlateWidget->UMGWidget.IsValid())
{
OutGazeFocusData.FocusedWidget = TobiiSlateWidget->UMGWidget;
break;
}
}
}
}
}
return OutGazeFocusData.FocusedActor != nullptr;
}
void UTobiiGTOMBlueprintLibrary::EmulateGazeFocusUsingWidgetInteractionComponent(UWidgetInteractionComponent* WidgetInteraction, ECollisionChannel CollisionChannel)
{
if (FTobiiGTOMModule::IsAvailable())
{
FTobiiGazeFocusData GazeFocusData;
MakeGazeFocusDataForWidgetInteractionComponent(WidgetInteraction, CollisionChannel, GazeFocusData);
TArray<FTobiiGazeFocusData> SingleFocusData;
SingleFocusData.Add(GazeFocusData);
FTobiiGTOMModule::Get().GTOMInputDevice->EmulateGazeFocus(SingleFocusData);
}
}
bool UTobiiGTOMBlueprintLibrary::FindLargestInscribedAlignedRect(float CircleSegmentAngleRad, float CircleRadius, FVector2D& LargestInscribedRectSize, float& DistanceToCenter)
{
//https://math.stackexchange.com/questions/2829710/largest-inscribed-rectangle-in-sector
if (CircleSegmentAngleRad > FLT_EPSILON && CircleRadius > FLT_EPSILON)
{
if (CircleSegmentAngleRad >= PI - FLT_EPSILON)
{
const float SqrtTwo = FMath::Sqrt(2.0f);
const float SqrtTwoOverTwo = SqrtTwo / 2.0f;
LargestInscribedRectSize.Set(SqrtTwo * CircleRadius, SqrtTwoOverTwo * CircleRadius);
DistanceToCenter = CircleRadius / 2.0f;
}
else
{
const float Theta = CircleSegmentAngleRad / 4.0f;
float ThetaSine, ThetaCosine;
FMath::SinCos(&ThetaSine, &ThetaCosine, Theta);
const float Height = CircleRadius * ThetaSine * 2.0f;
const float AlongX = CircleRadius * ThetaCosine;
const float HalfAlpha = CircleSegmentAngleRad / 2.0f;
float HalfAlphaSine, HalfAlphaCosine;
FMath::SinCos(&HalfAlphaSine, &HalfAlphaCosine, HalfAlpha);
const float B = CircleRadius * ThetaSine * HalfAlphaCosine / HalfAlphaSine;
LargestInscribedRectSize.Set(AlongX - B, Height);
DistanceToCenter = B + FMath::Min(LargestInscribedRectSize.X, LargestInscribedRectSize.Y) / 2.0f;
}
return true;
}
LargestInscribedRectSize.Set(0.0f, 0.0f);
DistanceToCenter = 0.0f;
return false;
}
bool UTobiiGTOMBlueprintLibrary::TransformWidgetLocalPointToWorld(UWidgetComponent* Component, const FVector2D& LocalWidgetLocation, FVector& OutWorldLocation)
{
if (Component == nullptr)
{
return false;
}
EWidgetGeometryMode GeometryMode = Component->GetGeometryMode();
switch (GeometryMode)
{
case EWidgetGeometryMode::Plane:
{
//First let's calculate some measurements we will need
const FVector2D DrawSize = Component->GetDrawSize();
const FVector2D Pivot = Component->GetPivot();
//Undo the pivot and transform to world space
OutWorldLocation = Component->GetComponentTransform().TransformPosition(FVector(0.0f, DrawSize.X * Pivot.X - LocalWidgetLocation.X, DrawSize.Y * Pivot.Y - LocalWidgetLocation.Y));
return true;
}
case EWidgetGeometryMode::Cylinder:
{
//First let's calculate some measurements we will need
const FVector2D DrawSize = Component->GetDrawSize();
const FVector2D Pivot = Component->GetPivot();
const float NormalizedLocationX = LocalWidgetLocation.X / DrawSize.X;
const float ArcAngleRadians = FMath::DegreesToRadians(Component->GetCylinderArcAngle());
const float Radius = DrawSize.X / ArcAngleRadians;
const float Apothem = Radius * FMath::Cos(0.5f * ArcAngleRadians);
const float ChordLength = 2.0f * Radius * FMath::Sin(0.5f * ArcAngleRadians);
const float PivotOffsetX = ChordLength * (0.5 - Pivot.X);
//Determine the endpoints of the UI surface in UI space radians. The projection is "reversed" here so the UI surface segment is actually centered around X-.
const float Endpoint1 = FMath::Fmod(FMath::Atan2(-0.5f * ChordLength, -Apothem) + 2 * PI, 2 * PI);
const float Endpoint2 = FMath::Fmod(FMath::Atan2(+0.5f * ChordLength, -Apothem) + 2 * PI, 2 * PI);
//Figure out where on the circle segment our X is using our normalized coordinate
const float InterpolatedAngle = FMath::Lerp(Endpoint1, Endpoint2, NormalizedLocationX);
//We can now determine our coordinate in circle space (the space where the origin is the center of the cylinder circle Y-segment the X-coordinate we are seeking is on) as well as the distance to the cylinder surface.
const float CircleSpaceWidgetX = Radius * FMath::Cos(InterpolatedAngle);
const float CircleSpaceWidgetDistanceY = Radius * FMath::Sin(InterpolatedAngle);
//Figure out our Z coordinate in widget space. This is easy since it's a cylinder.
const float WidgetSpaceZ = DrawSize.Y * Pivot.Y - LocalWidgetLocation.Y;
//We now convert the circle space coordinates to widget space (The origin is located at the center of the corda, in the middle of the widget component plane) and insert the widget space Z.
const FVector WidgetSpaceCoord(Apothem + CircleSpaceWidgetX, -CircleSpaceWidgetDistanceY + PivotOffsetX, WidgetSpaceZ);
//Finally we can simply transform this location to world space and we're done.
OutWorldLocation = Component->GetComponentTransform().TransformPosition(WidgetSpaceCoord);
return true;
}
default:
break;
}
return false;
}
bool UTobiiGTOMBlueprintLibrary::TransformWorldPointToWidgetLocal(UWidgetComponent* Component, const FVector& WorldLocation, const FVector& WorldDirection, FVector2D& OutLocalWidgetLocation)
{
if (Component == nullptr)
{
return false;
}
EWidgetGeometryMode GeometryMode = Component->GetGeometryMode();
switch (GeometryMode)
{
case EWidgetGeometryMode::Plane:
{
Component->GetLocalHitLocation(WorldLocation, OutLocalWidgetLocation);
return true;
}
case EWidgetGeometryMode::Cylinder:
{
TTuple<FVector, FVector2D> Output = Component->GetCylinderHitLocation(WorldLocation, WorldDirection);
OutLocalWidgetLocation = Output.Value;
return true;
}
default:
break;
}
return false;
}
bool UTobiiGTOMBlueprintLibrary::TestConeSphereIntersection(const FVector& ConeApex, const FVector& ConeDirection, const float ConeAngleDeg, const FVector& SphereCenter, const float SphereRadius)
{
//http://blog.julien.cayzac.name/2009/12/frustum-culling-sphere-cone-test-with.html
float ConeAngleSine, ConeAngleCosine;
FMath::SinCos(&ConeAngleSine, &ConeAngleCosine, FMath::DegreesToRadians(ConeAngleDeg));
const FVector DirectionTowardsSphere = SphereCenter - ConeApex;
const float SelfDot = FVector::DotProduct(DirectionTowardsSphere, DirectionTowardsSphere);
const float a = FVector::DotProduct(DirectionTowardsSphere, ConeDirection);
const float p(a*ConeAngleSine);
const float q(ConeAngleCosine * ConeAngleCosine * SelfDot - a * a);
const bool tmp(q < 0);
float lhs[2];
lhs[0] = (SphereRadius*SphereRadius - q);
lhs[1] = -lhs[0];
int result = (lhs[(p < SphereRadius) || !tmp] < 2.0f * SphereRadius * p) ? -1 : tmp;
return result != 0; // -1 means partially included. 1 means fully included
}
bool UTobiiGTOMBlueprintLibrary::TestRectEllipseIntersection(const FVector2D& RectangleCenter, const FVector2D& RectangleRightAxis, const FVector2D& RectangleUpAxis, const FVector2D& RectangleExtents, const FVector2D& EllipseCenter, const FVector2D& EllipseRadii, const float& EllipseRotationDeg)
{
//https://www.geometrictools.com/Documentation/IntersectionRectangleEllipse.pdf :: Minkowski sum
//Compute the increase in extents for R'.
float EllipseSine, EllipseCosine;
FMath::SinCos(&EllipseSine, &EllipseCosine, FMath::DegreesToRadians(EllipseRotationDeg));
FMatrix2x2 V(EllipseCosine, -EllipseSine, EllipseSine, EllipseCosine);
FMatrix2x2 VT(EllipseCosine, EllipseSine, -EllipseSine, EllipseCosine);
FMatrix2x2 D(1.0f / EllipseRadii.X, 0.0f, 0.0f, 1.0f / EllipseRadii.Y);
FMatrix2x2 M = V.Concatenate(D).Concatenate(D).Concatenate(VT);
FMatrix2x2 MInv = M.Inverse();
const float LRight = FMath::Sqrt(FVector2D::DotProduct(RectangleRightAxis, MInv.TransformVector(RectangleRightAxis)));
const float LUp = FMath::Sqrt(FVector2D::DotProduct(RectangleUpAxis, MInv.TransformVector(RectangleUpAxis)));
//Transform the ellipse center to the rectangle coordinate system.
FVector2D KmC = EllipseCenter - RectangleCenter;
float xi[2] = { FVector2D::DotProduct(RectangleRightAxis, KmC), FVector2D::DotProduct(RectangleUpAxis, KmC) };
if (FMath::Abs(xi[0]) <= RectangleExtents.X + LRight && FMath::Abs(xi[1]) <= RectangleExtents.Y + LUp)
{
float s[2] = { (xi[0] >= 0.0f ? 1.0f : -1.0f), (xi[1] >= 0.0f ? 1.0f : -1.0f) };
FVector2D PmC = s[0] * RectangleExtents.X * RectangleRightAxis + s[1] * RectangleExtents.X * RectangleUpAxis;
FVector2D MDelta = M.TransformVector(KmC - PmC);
if (s[0] * FVector2D::DotProduct(RectangleRightAxis, MDelta) <= 0.0f
|| s[1] * FVector2D::DotProduct(RectangleUpAxis, MDelta) <= 0.0f)
{
return true;
}
return FVector2D::DotProduct(MDelta, M.TransformVector(MDelta)) <= 1.0f;
}
return false;
}
FVector2D UTobiiGTOMBlueprintLibrary::GetUniformlyDistributedRandomCirclePoint(float CircleRadius)
{
float Angle = FMath::RandRange(0.0f, TWO_PI);
float Radius = CircleRadius * FMath::Sqrt(FMath::RandRange(0.0f, 1.0f));
return FVector2D(Radius * FMath::Cos(Angle), Radius * FMath::Sin(Angle));
}

View File

@@ -0,0 +1,631 @@
/******************************************************************************
* Copyright 2017- Tobii Technology AB. All rights reserved.
*
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
******************************************************************************/
#include "TobiiGTOMEngine.h"
#include "TobiiGazeFocusableComponent.h"
#include "TobiiGazeFocusableWidget.h"
#include "TobiiGTOMBlueprintLibrary.h"
#include "TobiiGTOMInternalTypes.h"
#include "Engine/Engine.h"
#include "IEyeTracker.h"
#include "DrawDebugHelpers.h"
#include "GameFramework/PlayerController.h"
#include "Runtime/Engine/Public/Slate/SceneViewport.h"
#include "Misc/Paths.h"
static TAutoConsoleVariable<int32> CVarDebugDisplayGTOMVisibility(TEXT("tobii.debug.DisplayGTOMVisibility"), 0, TEXT("1 means we visualize which objects are visible to G2OM"));
static TAutoConsoleVariable<int32> CVarDebugDisplayG2OMCandidateSet(TEXT("tobii.debug.DisplayG2OMCandidateSet"), 1, TEXT("1 will visualize all bounds calculated in G2OM. This is useful to test for math errors."));
FTobiiGTOMEngine::FTobiiGTOMEngine()
{
g2om_context_create(&G2OMContext);
}
FTobiiGTOMEngine::~FTobiiGTOMEngine()
{
if (G2OMContext != nullptr)
{
g2om_context_destroy(&G2OMContext);
G2OMContext = nullptr;
}
}
void FTobiiGTOMEngine::Tick(float DeltaTimeSecs)
{
static const auto DrawDebugCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("tobii.debug"));
static const auto MaximumTraceDistanceCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("tobii.MaximumTraceDistance"));
static const auto FocusTraceChannelCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("tobii.FocusTraceChannel"));
if (GEngine == nullptr
|| GEngine->GameViewport == nullptr
|| GEngine->GameViewport->GetWorld() == nullptr
|| GEngine->GameViewport->GetGameViewport() == nullptr
|| !GEngine->EyeTrackingDevice.IsValid())
{
return;
}
//Gaze data
FEyeTrackerGazeData CombinedGazeData;
GEngine->EyeTrackingDevice->GetEyeTrackerGazeData(CombinedGazeData);
FVector2D CombinedGazePtUNorm = FVector2D::ZeroVector;
FVector2D ScreenGazePointPx;
FVector2D ViewportSize;
GEngine->GameViewport->GetViewportSize(ViewportSize);
GTOMPlayerController->ProjectWorldLocationToScreen(CombinedGazeData.GazeOrigin + CombinedGazeData.GazeDirection * 10.0f, ScreenGazePointPx);
if (ViewportSize.X > 0.0f && ViewportSize.Y > 0.0f)
{
CombinedGazePtUNorm = FVector2D(ScreenGazePointPx.X / ViewportSize.X, ScreenGazePointPx.Y / ViewportSize.Y);
}
if (!GTOMPlayerController.IsValid())
{
GTOMPlayerController = GEngine->GameViewport->GetWorld()->GetFirstPlayerController();
GEngine->EyeTrackingDevice->SetEyeTrackedPlayer(GTOMPlayerController.Get());
}
//Main G2OM processing
if (G2OMContext == nullptr
|| !GTOMPlayerController.IsValid()
|| GTOMPlayerController->PlayerCameraManager == nullptr)
{
return;
}
QUICK_SCOPE_CYCLE_COUNTER(STAT_TobiiEyetracking_GTOM);
FRotator CameraRotation = GTOMPlayerController->PlayerCameraManager->GetCameraRotation();
G2OMGazeData.timestamp_in_s = GTOMPlayerController->GetWorld()->GetTimeSeconds();
G2OMGazeData.camera_up_direction_world_space = FTobiiGTOMUtils::UE4VectorToG2OMVector(CameraRotation.RotateVector(FVector::UpVector));
G2OMGazeData.camera_right_direction_world_space = FTobiiGTOMUtils::UE4VectorToG2OMVector(CameraRotation.RotateVector(FVector::RightVector));
G2OMGazeData.gaze_ray_world_space.is_valid = CombinedGazeData.ConfidenceValue > 0.5f; // Sigh. Why is this still a thing?
G2OMGazeData.gaze_ray_world_space.ray.origin.x = CombinedGazeData.GazeOrigin.X;
G2OMGazeData.gaze_ray_world_space.ray.origin.y = CombinedGazeData.GazeOrigin.Y;
G2OMGazeData.gaze_ray_world_space.ray.origin.z = CombinedGazeData.GazeOrigin.Z;
G2OMGazeData.gaze_ray_world_space.ray.direction.x = CombinedGazeData.GazeDirection.X;
G2OMGazeData.gaze_ray_world_space.ray.direction.y = CombinedGazeData.GazeDirection.Y;
G2OMGazeData.gaze_ray_world_space.ray.direction.z = CombinedGazeData.GazeDirection.Z;
//Raycasts
FCollisionQueryParams CollisionQueryParams;
CollisionQueryParams.AddIgnoredActor(GTOMPlayerController.Get());
CollisionQueryParams.AddIgnoredActor(GTOMPlayerController->GetPawn());
const float MaximumTraceDistance = FMath::Max(MaximumTraceDistanceCVar->GetFloat(), 0.0f);
const FVector CombinedGazeFarLocation = CombinedGazeData.GazeOrigin + (CombinedGazeData.GazeDirection * MaximumTraceDistance);
if (CombinedGazeData.ConfidenceValue < 0.5f ||
!GTOMPlayerController->GetWorld()->LineTraceSingleByChannel(CombinedWorldGazeHitData, CombinedGazeData.GazeOrigin
, CombinedGazeFarLocation, (ECollisionChannel)FocusTraceChannelCVar->GetInt(), CollisionQueryParams))
{
CombinedWorldGazeHitData.Actor = nullptr;
CombinedWorldGazeHitData.Component = nullptr;
CombinedWorldGazeHitData.Distance = MaximumTraceDistance;
CombinedWorldGazeHitData.Location = CombinedGazeFarLocation;
CombinedWorldGazeHitData.bBlockingHit = false;
}
FillG2OMRaycast(CombinedWorldGazeHitData, CombinedGazePtUNorm, CombinedGazeData.GazeDirection, G2OMRaycastResults.raycast);
//Candidate generation
TArray<g2om_candidate> Candidates;
TArray<g2om_candidate_result> CandidateResults;
TMap<FEngineFocusableUID, TWeakObjectPtr<UTobiiGazeFocusableComponent>> FocusableComponentsWithWidgets;
const TMap<FEngineFocusableUID, FTobiiGTOMOcclusionData>& VisibleSet = OcclusionTester.Tick(DeltaTimeSecs, GTOMPlayerController.Get(), CombinedGazeData, G2OMGazeData, G2OMContext);
TMap<FTobiiFocusableUID, TWeakObjectPtr<UTobiiGazeFocusableWidget>>& ScreenSpaceWidgets = UTobiiGazeFocusableWidget::GetTobiiScreenSpaceFocusableWidgets();
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_TobiiEyetracking_GTOM_BuildCandidates);
//We must do this every frame since any of these properties might have changed since the last tick.
for (auto OcclusionDataIterator = VisibleSet.CreateConstIterator(); OcclusionDataIterator; ++OcclusionDataIterator)
{
const FTobiiGTOMOcclusionData& OcclusionData = OcclusionDataIterator.Value();
const FEngineFocusableUID FocusableID = OcclusionDataIterator.Key();
if (OcclusionData.PrimitiveComponent.IsValid() && OcclusionData.PrimitiveComponent->IsVisible())
{
bool bShouldAddPrimitive = true;
if (OcclusionData.FocusableComponent.IsValid())
{
const TArray<TWeakObjectPtr<UTobiiGazeFocusableWidget>>& FocusableWidgets = OcclusionData.FocusableComponent->GetFocusableWidgetsForPrimitiveComponent(OcclusionData.PrimitiveComponent.Get());
if (FocusableWidgets.Num() > 0)
{
//If our primitive contains widgets, then only add the widgets
bShouldAddPrimitive = false;
g2om_candidate NewCandidate;
FTransform LocalToWorldTranform = OcclusionData.PrimitiveComponent->GetComponentTransform();
FMatrix LocalToWorldMatrix = LocalToWorldTranform.ToMatrixWithScale();
FMatrix WorldToLocalMatrix = LocalToWorldMatrix.InverseFast();
FMemory::Memcpy(NewCandidate.local_to_world_matrix.data, LocalToWorldMatrix.M, 16 * sizeof(float));
FMemory::Memcpy(NewCandidate.world_to_local_matrix.data, WorldToLocalMatrix.M, 16 * sizeof(float));
for (const auto& Widget : FocusableWidgets)
{
if (Widget.IsValid() && !FocusableComponentsWithWidgets.Contains(Widget->GetUniqueID())
&& UTobiiGazeFocusableComponent::IsWidgetFocusable(Widget.Get())
&& Widget->GetWorldSpaceHostWidgetComponent() != nullptr)
{
STobiiGazeFocusableWidget* SlateContainer = (STobiiGazeFocusableWidget*)&Widget->TakeWidget().Get();
FSlateRect WidgetRenderBounds = SlateContainer->GetCachedGeometry().GetRenderBoundingRect();
FVector WidgetTopLeft, WidgetBottomRight;
UTobiiGTOMBlueprintLibrary::TransformWidgetLocalPointToWorld(Widget->GetWorldSpaceHostWidgetComponent(), WidgetRenderBounds.GetTopLeft(), WidgetTopLeft);
UTobiiGTOMBlueprintLibrary::TransformWidgetLocalPointToWorld(Widget->GetWorldSpaceHostWidgetComponent(), WidgetRenderBounds.GetBottomRight(), WidgetBottomRight);
//Range test
bool bIsInRange = true;
float MaxDistance;
const float DistanceToWidget = FVector::Distance(CombinedGazeData.GazeOrigin, (WidgetTopLeft + WidgetBottomRight) / 2.0f);
if (UTobiiGazeFocusableComponent::GetMaxFocusDistanceForWidget(Widget.Get(), MaxDistance))
{
bIsInRange = DistanceToWidget <= MaxDistance;
}
if (bIsInRange)
{
WidgetTopLeft = WorldToLocalMatrix.TransformPosition(WidgetTopLeft);
WidgetBottomRight = WorldToLocalMatrix.TransformPosition(WidgetBottomRight);
NewCandidate.id = Widget->GetUniqueID();
NewCandidate.min_local_space.x = WidgetTopLeft.X;
NewCandidate.min_local_space.y = WidgetTopLeft.Y;
NewCandidate.min_local_space.z = WidgetTopLeft.Z;
NewCandidate.max_local_space.x = WidgetBottomRight.X;
NewCandidate.max_local_space.y = WidgetBottomRight.Y;
NewCandidate.max_local_space.z = WidgetBottomRight.Z;
Candidates.Add(NewCandidate);
CandidateResults.Add(g2om_candidate_result());
FocusableComponentsWithWidgets.Add(Widget->GetUniqueID(), OcclusionData.FocusableComponent);
}
}
}
}
}
if(bShouldAddPrimitive)
{
//If there were no widgets, just add the primitive.
g2om_candidate NewCandidate;
NewCandidate.id = FocusableID;
FTransform LocalToWorldTranform = OcclusionData.PrimitiveComponent->GetComponentTransform();
FMatrix LocalToWorldMatrix = LocalToWorldTranform.ToMatrixWithScale();
FMatrix WorldToLocalMatrix = LocalToWorldMatrix.InverseFast();
FMemory::Memcpy(NewCandidate.local_to_world_matrix.data, LocalToWorldMatrix.M, 16 * sizeof(float));
FMemory::Memcpy(NewCandidate.world_to_local_matrix.data, WorldToLocalMatrix.M, 16 * sizeof(float));
FBox BoxBounds = OcclusionData.PrimitiveComponent->CalcBounds(FTransform::Identity).GetBox();
NewCandidate.min_local_space.x = BoxBounds.Min.X;
NewCandidate.min_local_space.y = BoxBounds.Min.Y;
NewCandidate.min_local_space.z = BoxBounds.Min.Z;
NewCandidate.max_local_space.x = BoxBounds.Max.X;
NewCandidate.max_local_space.y = BoxBounds.Max.Y;
NewCandidate.max_local_space.z = BoxBounds.Max.Z;
Candidates.Add(MoveTemp(NewCandidate));
CandidateResults.Add(g2om_candidate_result());
}
}
}
//Finally add registered screen space widgets as candidates.
TArray<FTobiiFocusableUID> StaleWidgetIds;
for (auto& WidgetPair : ScreenSpaceWidgets)
{
TWeakObjectPtr<UTobiiGazeFocusableWidget> Widget = WidgetPair.Value;
if (Widget.IsValid())
{
if (!FocusableComponentsWithWidgets.Contains(Widget->GetUniqueID()) && UTobiiGazeFocusableComponent::IsWidgetFocusable(Widget.Get()))
{
STobiiGazeFocusableWidget* SlateContainer = (STobiiGazeFocusableWidget*)&Widget->TakeWidget().Get();
FSlateRect WidgetRenderBounds = SlateContainer->GetCachedGeometry().GetRenderBoundingRect();
FVector2D ScreenTopLeft = GEngine->GameViewport->GetGameViewport()->VirtualDesktopPixelToViewport(FIntPoint(WidgetRenderBounds.GetTopLeft().X, WidgetRenderBounds.GetTopLeft().Y));
FVector2D ScreenBottomRight = GEngine->GameViewport->GetGameViewport()->VirtualDesktopPixelToViewport(FIntPoint(WidgetRenderBounds.GetBottomRight().X, WidgetRenderBounds.GetBottomRight().Y));
ScreenTopLeft.X *= ViewportSize.X;
ScreenTopLeft.Y *= ViewportSize.Y;
ScreenBottomRight.X *= ViewportSize.X;
ScreenBottomRight.Y *= ViewportSize.Y;
FVector2D ScreenBottomLeft(ScreenTopLeft.X, ScreenBottomRight.Y);
FVector WorldTopLeftLocation, WorldBottomRightLocation, WorldBottomLeftLocation;
FVector WorldTopLeftDir, WorldBottomRightDir, WorldBottomLeftDir;
GTOMPlayerController->DeprojectScreenPositionToWorld(ScreenTopLeft.X, ScreenTopLeft.Y, WorldTopLeftLocation, WorldTopLeftDir);
GTOMPlayerController->DeprojectScreenPositionToWorld(ScreenBottomRight.X, ScreenBottomRight.Y, WorldBottomRightLocation, WorldBottomRightDir);
GTOMPlayerController->DeprojectScreenPositionToWorld(ScreenBottomLeft.X, ScreenBottomLeft.Y, WorldBottomLeftLocation, WorldBottomLeftDir);
WorldTopLeftLocation += WorldTopLeftDir;
WorldBottomRightLocation += WorldBottomRightDir;
WorldBottomLeftLocation += WorldBottomLeftDir;
FVector WorldTranslation = (WorldTopLeftLocation + WorldBottomRightLocation) / 2.0f;
FVector WorldRightY = WorldBottomRightLocation - WorldBottomLeftLocation;
FVector WorldUpZ = WorldTopLeftLocation - WorldBottomLeftLocation;
FVector WorldForwardX = FVector::CrossProduct(WorldRightY, WorldUpZ);
bool bCouldBuildOrthoNormalBasis = WorldForwardX.Normalize();
bCouldBuildOrthoNormalBasis = bCouldBuildOrthoNormalBasis && WorldRightY.Normalize();
bCouldBuildOrthoNormalBasis = bCouldBuildOrthoNormalBasis && WorldUpZ.Normalize();
if (bCouldBuildOrthoNormalBasis)
{
FMatrix LocalToWorldMatrix = FMatrix(WorldForwardX, WorldRightY, WorldUpZ, WorldTranslation);
FMatrix WorldToLocalMatrix = LocalToWorldMatrix.InverseFast();
WorldTopLeftLocation = WorldToLocalMatrix.TransformPosition(WorldTopLeftLocation + WorldTopLeftDir);
WorldBottomRightLocation = WorldToLocalMatrix.TransformPosition(WorldBottomRightLocation + WorldBottomRightDir);
//Screen space widgets are always in range, so we can skip the distance test
g2om_candidate NewCandidate;
NewCandidate.id = Widget->GetUniqueID();
FMemory::Memcpy(NewCandidate.local_to_world_matrix.data, LocalToWorldMatrix.M, 16 * sizeof(float));
FMemory::Memcpy(NewCandidate.world_to_local_matrix.data, WorldToLocalMatrix.M, 16 * sizeof(float));
NewCandidate.min_local_space.x = WorldTopLeftLocation.X;
NewCandidate.min_local_space.y = WorldTopLeftLocation.Y;
NewCandidate.min_local_space.z = WorldTopLeftLocation.Z;
NewCandidate.max_local_space.x = WorldBottomRightLocation.X;
NewCandidate.max_local_space.y = WorldBottomRightLocation.Y;
NewCandidate.max_local_space.z = WorldBottomRightLocation.Z;
Candidates.Add(NewCandidate);
CandidateResults.Add(g2om_candidate_result());
}
}
}
else
{
StaleWidgetIds.Add(WidgetPair.Key);
}
for (FTobiiFocusableUID Id : StaleWidgetIds)
{
ScreenSpaceWidgets.Remove(Id);
}
}
}
UPrimitiveComponent* NewTopFocusPrimitive = nullptr;
UTobiiGazeFocusableWidget* NewTopFocusWidget = nullptr;
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_TobiiEyetracking_GTOM_G2OMProcess);
g2om_process(G2OMContext, &G2OMGazeData, &G2OMRaycastResults, Candidates.Num(), Candidates.GetData(), CandidateResults.GetData());
G2OMFocusResults.Empty();
for (const g2om_candidate_result& ResultCandidate : CandidateResults)
{
if (ResultCandidate.score > FLT_EPSILON)
{
if(ScreenSpaceWidgets.Contains(ResultCandidate.id))
{
//Screen space widget
TWeakObjectPtr<UTobiiGazeFocusableWidget> Widget = ScreenSpaceWidgets[ResultCandidate.id];
if (Widget.IsValid())
{
FTobiiGazeFocusData NewFocusData;
NewFocusData.FocusedWidget = Widget;
NewFocusData.FocusConfidence = ResultCandidate.score;
NewFocusData.FocusedActor = nullptr;
NewFocusData.FocusedPrimitiveComponent = nullptr;
NewFocusData.LastVisibleWorldLocation = FVector::ZeroVector;
G2OMFocusResults.Add(MoveTemp(NewFocusData));
if (NewTopFocusWidget == nullptr)
{
NewTopFocusWidget = Widget.Get();
}
}
}
else if (FocusableComponentsWithWidgets.Contains(ResultCandidate.id))
{
//World space widget
TWeakObjectPtr<UTobiiGazeFocusableComponent> Focusable = FocusableComponentsWithWidgets[ResultCandidate.id];
if (Focusable.IsValid())
{
const TMap<uint32, TWeakObjectPtr<UTobiiGazeFocusableWidget>>& FocusableWidgets = Focusable->GetAllFocusableWidgets();
if (FocusableWidgets.Contains(ResultCandidate.id))
{
TWeakObjectPtr<UTobiiGazeFocusableWidget> Widget = FocusableWidgets[ResultCandidate.id];
if (Widget.IsValid())
{
FTobiiGazeFocusData NewFocusData;
NewFocusData.FocusedWidget = Widget;
NewFocusData.FocusConfidence = ResultCandidate.score;
if (Widget->GetWorldSpaceHostWidgetComponent() != nullptr)
{
NewFocusData.FocusedActor = Widget->GetWorldSpaceHostWidgetComponent()->GetOwner();
NewFocusData.FocusedPrimitiveComponent = Widget->GetWorldSpaceHostWidgetComponent();
NewFocusData.LastVisibleWorldLocation = Widget->GetWorldSpaceHostWidgetComponent()->GetComponentLocation();
}
G2OMFocusResults.Add(MoveTemp(NewFocusData));
if (NewTopFocusWidget == nullptr)
{
NewTopFocusWidget = Widget.Get();
}
}
}
}
}
else if(VisibleSet.Contains(ResultCandidate.id))
{
//Primitive Component
TWeakObjectPtr<UPrimitiveComponent> FocusedPrimitivePtr = VisibleSet[ResultCandidate.id].PrimitiveComponent;
if (FocusedPrimitivePtr.IsValid())
{
FTobiiGazeFocusData NewFocusData;
NewFocusData.FocusedActor = FocusedPrimitivePtr->GetOwner();
NewFocusData.FocusedPrimitiveComponent = FocusedPrimitivePtr;
NewFocusData.FocusedWidget = nullptr;
UTobiiGTOMBlueprintLibrary::GetPrimitiveComponentFocusLocation(FocusedPrimitivePtr.Get(), NewFocusData.LastVisibleWorldLocation); // We want this to be taken care of by GXOM, but the current system does not support it.
NewFocusData.FocusConfidence = ResultCandidate.score;
G2OMFocusResults.Add(MoveTemp(NewFocusData));
if (NewTopFocusPrimitive == nullptr)
{
NewTopFocusPrimitive = FocusedPrimitivePtr.Get();
}
}
}
}
}
}
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_TobiiEyetracking_GTOM_G2OMNotify);
UpdateWinners(NewTopFocusPrimitive, NewTopFocusWidget);
}
if (DrawDebugCVar->GetInt() && CVarDebugDisplayG2OMCandidateSet.GetValueOnGameThread() != 0)
{
for (g2om_candidate& Candidate : Candidates)
{
bool bIsFocused = false;
float FocusAlpha = 1.0f;
for (const g2om_candidate_result& ResultCandidate : CandidateResults)
{
if (ResultCandidate.id == Candidate.id && ResultCandidate.score > FLT_EPSILON)
{
bIsFocused = true;
FocusAlpha = ResultCandidate.score;
break;
}
}
g2om_vector Corners[G2OM_CORNERS_COUNT];
g2om_get_worldspace_corner_of_candidate(&Candidate, G2OM_CORNERS_COUNT, Corners);
TArray<FVector> UCorners;
for (int32 Idx = 0; Idx < G2OM_CORNERS_COUNT; Idx++)
{
UCorners.Add(FTobiiGTOMUtils::G2OMVectorToUE4Vector(Corners[Idx]));
}
UWorld* World = GTOMPlayerController->GetWorld();
const float DistanceToFirstCorner = FVector::Distance(UCorners[G2OM_CORNERS_FLL], GTOMPlayerController->GetPawn()->GetActorLocation());
const float Thickness = (bIsFocused && DistanceToFirstCorner > 100.0f) ? 3.0f : 0.0f;
const FColor BoxColor = bIsFocused ? FColor(0, 255, 0, FocusAlpha * 255)
: (FocusableComponentsWithWidgets.Contains(Candidate.id) ? FColor::Cyan : FColor::Orange);
//FRONT
DrawDebugLine(World, UCorners[G2OM_CORNERS_FLL], UCorners[G2OM_CORNERS_FLR], BoxColor, false, 0.0f, 0, Thickness);
DrawDebugLine(World, UCorners[G2OM_CORNERS_FLR], UCorners[G2OM_CORNERS_FUR], BoxColor, false, 0.0f, 0, Thickness);
DrawDebugLine(World, UCorners[G2OM_CORNERS_FUR], UCorners[G2OM_CORNERS_FUL], BoxColor, false, 0.0f, 0, Thickness);
DrawDebugLine(World, UCorners[G2OM_CORNERS_FUL], UCorners[G2OM_CORNERS_FLL], BoxColor, false, 0.0f, 0, Thickness);
//BACK
DrawDebugLine(World, UCorners[G2OM_CORNERS_BLL], UCorners[G2OM_CORNERS_BLR], BoxColor, false, 0.0f, 0, Thickness);
DrawDebugLine(World, UCorners[G2OM_CORNERS_BLR], UCorners[G2OM_CORNERS_BUR], BoxColor, false, 0.0f, 0, Thickness);
DrawDebugLine(World, UCorners[G2OM_CORNERS_BUR], UCorners[G2OM_CORNERS_BUL], BoxColor, false, 0.0f, 0, Thickness);
DrawDebugLine(World, UCorners[G2OM_CORNERS_BUL], UCorners[G2OM_CORNERS_BLL], BoxColor, false, 0.0f, 0, Thickness);
//SPOKES
DrawDebugLine(World, UCorners[G2OM_CORNERS_FLL], UCorners[G2OM_CORNERS_BLL], BoxColor, false, 0.0f, 0, Thickness);
DrawDebugLine(World, UCorners[G2OM_CORNERS_FLR], UCorners[G2OM_CORNERS_BLR], BoxColor, false, 0.0f, 0, Thickness);
DrawDebugLine(World, UCorners[G2OM_CORNERS_FUR], UCorners[G2OM_CORNERS_BUR], BoxColor, false, 0.0f, 0, Thickness);
DrawDebugLine(World, UCorners[G2OM_CORNERS_FUL], UCorners[G2OM_CORNERS_BUL], BoxColor, false, 0.0f, 0, Thickness);
}
}
}
void FTobiiGTOMEngine::UpdateWinners(UPrimitiveComponent* NewTopFocusPrimitive, UTobiiGazeFocusableWidget* NewTopFocusWidget)
{
//Primitive components
{
if (NewTopFocusPrimitive == nullptr && PreviouslyFocusedPrimitiveComponent.IsValid())
{
//We lost focus completely
NotifyPrimitiveComponentGazeFocusLost(*PreviouslyFocusedPrimitiveComponent.Get());
}
else if (NewTopFocusPrimitive != nullptr && !PreviouslyFocusedPrimitiveComponent.IsValid())
{
//We gained focus from no focus
NotifyPrimitiveComponentGazeFocusReceived(*NewTopFocusPrimitive);
}
else if (NewTopFocusPrimitive != nullptr && PreviouslyFocusedPrimitiveComponent.IsValid() && NewTopFocusPrimitive != PreviouslyFocusedPrimitiveComponent.Get())
{
//Focus shifted from one object to another
NotifyPrimitiveComponentGazeFocusLost(*PreviouslyFocusedPrimitiveComponent.Get());
NotifyPrimitiveComponentGazeFocusReceived(*NewTopFocusPrimitive);
}
PreviouslyFocusedPrimitiveComponent = TWeakObjectPtr<UPrimitiveComponent>(NewTopFocusPrimitive);
}
//Widgets
{
if (NewTopFocusWidget == nullptr && PreviouslyFocusedWidget.IsValid())
{
//We lost focus completely
NotifyWidgetGazeFocusLost(*PreviouslyFocusedWidget.Get());
}
else if (NewTopFocusWidget != nullptr && !PreviouslyFocusedWidget.IsValid())
{
//We gained focus from no focus
NotifyWidgetGazeFocusReceived(*NewTopFocusWidget);
}
else if (NewTopFocusWidget != nullptr && PreviouslyFocusedWidget.IsValid() && NewTopFocusWidget != PreviouslyFocusedWidget.Get())
{
//Focus shifted from one object to another
NotifyWidgetGazeFocusLost(*PreviouslyFocusedWidget.Get());
NotifyWidgetGazeFocusReceived(*NewTopFocusWidget);
}
PreviouslyFocusedWidget = TWeakObjectPtr<UTobiiGazeFocusableWidget>(NewTopFocusWidget);
}
}
void FTobiiGTOMEngine::FillG2OMRaycast(const FHitResult& HitResult, const FVector2D& ScreenGazePointUNorm, const FVector& GazeDirection, g2om_raycast& RaycastToModify)
{
RaycastToModify.is_valid = false;
RaycastToModify.id = -1;
if (GEngine == nullptr
|| GEngine->GameViewport == nullptr
|| GEngine->GameViewport->GetWorld() == nullptr
|| GEngine->GameViewport->GetGameViewport() == nullptr
|| !GEngine->EyeTrackingDevice.IsValid())
{
return;
}
//First test screen space widgets
FVector2D VirtualDesktopGazePoint = GEngine->GameViewport->GetGameViewport()->ViewportToVirtualDesktopPixel(ScreenGazePointUNorm);
TMap<FTobiiFocusableUID, TWeakObjectPtr<UTobiiGazeFocusableWidget>>& ScreenSpaceWidgets = UTobiiGazeFocusableWidget::GetTobiiScreenSpaceFocusableWidgets();
for (auto& WidgetPair : ScreenSpaceWidgets)
{
TWeakObjectPtr<UTobiiGazeFocusableWidget> Widget = WidgetPair.Value;
if (Widget.IsValid())
{
if (UTobiiGazeFocusableComponent::IsWidgetFocusable(Widget.Get()))
{
STobiiGazeFocusableWidget* SlateContainer = (STobiiGazeFocusableWidget*)&Widget->TakeWidget().Get();
FSlateRect WidgetRenderBounds = SlateContainer->GetCachedGeometry().GetRenderBoundingRect();
if (WidgetRenderBounds.ContainsPoint(VirtualDesktopGazePoint))
{
RaycastToModify.is_valid = true;
RaycastToModify.id = Widget->GetUniqueID();
return;
}
}
}
}
UPrimitiveComponent* Primitive = HitResult.GetComponent();
if (Primitive != nullptr)
{
RaycastToModify.is_valid = UTobiiGazeFocusableComponent::IsPrimitiveFocusable(Primitive);
if (Primitive->GetOwner())
{
UTobiiGazeFocusableComponent* FocusableComponent = (UTobiiGazeFocusableComponent*)Primitive->GetOwner()->GetComponentByClass(UTobiiGazeFocusableComponent::StaticClass());
if (FocusableComponent != nullptr)
{
const TArray<TWeakObjectPtr<UTobiiGazeFocusableWidget>>& FocusableWidgets = FocusableComponent->GetFocusableWidgetsForPrimitiveComponent(Primitive);
for (const auto& Widget : FocusableWidgets)
{
if (Widget.IsValid() && UTobiiGazeFocusableComponent::IsWidgetFocusable(Widget.Get()))
{
FVector2D WidgetLocation;
if (UTobiiGTOMBlueprintLibrary::TransformWorldPointToWidgetLocal(Widget->GetWorldSpaceHostWidgetComponent(), HitResult.Location, GazeDirection, WidgetLocation))
{
STobiiGazeFocusableWidget* SlateContainer = (STobiiGazeFocusableWidget*)&Widget->TakeWidget().Get();
FSlateRect WidgetRenderBounds = SlateContainer->GetCachedGeometry().GetRenderBoundingRect();
if (WidgetRenderBounds.ContainsPoint(WidgetLocation))
{
//We found a hit!
RaycastToModify.id = Widget->GetUniqueID();
return;
}
}
}
}
}
}
//Default
RaycastToModify.id = Primitive->GetUniqueID();
}
}
void FTobiiGTOMEngine::NotifyPrimitiveComponentGazeFocusReceived(UPrimitiveComponent& PrimitiveComponentToReceiveFocus)
{
PrimitiveComponentToReceiveFocus.ComponentTags.AddUnique(FTobiiPrimitiveComponentGazeFocusTags::HasGazeFocusTag);
if (PrimitiveComponentToReceiveFocus.GetOwner() != nullptr)
{
UTobiiGazeFocusableComponent* GazeFocusableComponent = (UTobiiGazeFocusableComponent*)PrimitiveComponentToReceiveFocus.GetOwner()->GetComponentByClass(UTobiiGazeFocusableComponent::StaticClass());
if (GazeFocusableComponent != nullptr)
{
GazeFocusableComponent->PrimitiveReceivedGazeFocus(&PrimitiveComponentToReceiveFocus);
}
}
}
void FTobiiGTOMEngine::NotifyPrimitiveComponentGazeFocusLost(UPrimitiveComponent& PrimitiveComponentToLoseFocus)
{
PrimitiveComponentToLoseFocus.ComponentTags.Remove(FTobiiPrimitiveComponentGazeFocusTags::HasGazeFocusTag);
if (PrimitiveComponentToLoseFocus.GetOwner() != nullptr)
{
UTobiiGazeFocusableComponent* GazeFocusableComponent = (UTobiiGazeFocusableComponent*)PrimitiveComponentToLoseFocus.GetOwner()->GetComponentByClass(UTobiiGazeFocusableComponent::StaticClass());
if (GazeFocusableComponent != nullptr)
{
GazeFocusableComponent->PrimitiveLostGazeFocus(&PrimitiveComponentToLoseFocus);
}
}
}
void FTobiiGTOMEngine::NotifyWidgetGazeFocusReceived(UTobiiGazeFocusableWidget& WidgetToReceiveFocus)
{
WidgetToReceiveFocus.bHasGazeFocus = true;
WidgetToReceiveFocus.ReceiveFocus();
if (WidgetToReceiveFocus.GetWorldSpaceHostWidgetComponent() != nullptr && WidgetToReceiveFocus.GetWorldSpaceHostWidgetComponent()->GetOwner() != nullptr)
{
UTobiiGazeFocusableComponent* GazeFocusableComponent = (UTobiiGazeFocusableComponent*)WidgetToReceiveFocus.GetWorldSpaceHostWidgetComponent()->GetOwner()->GetComponentByClass(UTobiiGazeFocusableComponent::StaticClass());
if (GazeFocusableComponent != nullptr)
{
GazeFocusableComponent->WidgetReceivedGazeFocus(&WidgetToReceiveFocus);
}
}
}
void FTobiiGTOMEngine::NotifyWidgetGazeFocusLost(UTobiiGazeFocusableWidget& WidgetToLoseFocus)
{
WidgetToLoseFocus.bHasGazeFocus = false;
WidgetToLoseFocus.LoseFocus();
if (WidgetToLoseFocus.GetWorldSpaceHostWidgetComponent() != nullptr && WidgetToLoseFocus.GetWorldSpaceHostWidgetComponent()->GetOwner() != nullptr)
{
UTobiiGazeFocusableComponent* GazeFocusableComponent = (UTobiiGazeFocusableComponent*)WidgetToLoseFocus.GetWorldSpaceHostWidgetComponent()->GetOwner()->GetComponentByClass(UTobiiGazeFocusableComponent::StaticClass());
if (GazeFocusableComponent != nullptr)
{
GazeFocusableComponent->WidgetLostGazeFocus(&WidgetToLoseFocus);
}
}
}
void FTobiiGTOMEngine::EmulateGazeFocus(TArray<FTobiiGazeFocusData>& EmulatedFocusData)
{
G2OMFocusResults.Empty();
G2OMFocusResults = EmulatedFocusData;
UPrimitiveComponent* TopPrimitive = nullptr;
UTobiiGazeFocusableWidget* TopWidget = nullptr;
if (EmulatedFocusData.Num() > 0)
{
TopPrimitive = EmulatedFocusData[0].FocusedPrimitiveComponent.IsValid() ? EmulatedFocusData[0].FocusedPrimitiveComponent.Get() : nullptr;
TopWidget = EmulatedFocusData[0].FocusedWidget.IsValid() ? EmulatedFocusData[0].FocusedWidget.Get() : nullptr;
}
UpdateWinners(TopPrimitive, TopWidget);
}

View File

@@ -0,0 +1,55 @@
/******************************************************************************
* Copyright 2017- Tobii Technology AB. All rights reserved.
*
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
******************************************************************************/
#pragma once
#include "TobiiGTOMOcclusionTester.h"
#include "TobiiGazeFocusableWidget.h"
#include "TobiiGTOMTypes.h"
#include "tobii_g2om.h"
#include "CoreMinimal.h"
#include "Runtime/InputDevice/Public/IInputDevice.h"
class TOBIIGTOM_API FTobiiGTOMEngine : public IInputDevice
{
public:
FTobiiGTOMEngine();
virtual ~FTobiiGTOMEngine();
FHitResult CombinedWorldGazeHitData;
TWeakObjectPtr<APlayerController> GTOMPlayerController;
const TArray<FTobiiGazeFocusData>& GetFocusData() { return G2OMFocusResults; }
void EmulateGazeFocus(TArray<FTobiiGazeFocusData>& EmulatedFocusData);
// IInputDevice
public:
virtual void Tick(float DeltaTime) override;
virtual void SendControllerEvents() override { }
virtual void SetMessageHandler(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler) override { }
virtual bool Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) override { return true; }
virtual void SetChannelValue(int32 ControllerId, FForceFeedbackChannelType ChannelType, float Value) override { }
virtual void SetChannelValues(int32 ControllerId, const FForceFeedbackValues &values) override { }
private:
g2om_context* G2OMContext;
g2om_raycast_result G2OMRaycastResults;
g2om_gaze_data G2OMGazeData;
FTobiiGTOMOcclusionTester OcclusionTester;
TArray<FTobiiGazeFocusData> G2OMFocusResults;
TWeakObjectPtr<UPrimitiveComponent> PreviouslyFocusedPrimitiveComponent;
TWeakObjectPtr<UTobiiGazeFocusableWidget> PreviouslyFocusedWidget;
void UpdateWinners(UPrimitiveComponent* NewTopFocusPrimitive, UTobiiGazeFocusableWidget* NewTopFocusWidget);
void NotifyPrimitiveComponentGazeFocusReceived(UPrimitiveComponent& PrimitiveComponentToReceiveFocus);
void NotifyPrimitiveComponentGazeFocusLost(UPrimitiveComponent& PrimitiveComponentToLoseFocus);
void NotifyWidgetGazeFocusReceived(UTobiiGazeFocusableWidget& WidgetToReceiveFocus);
void NotifyWidgetGazeFocusLost(UTobiiGazeFocusableWidget& WidgetToLoseFocus);
void FillG2OMRaycast(const FHitResult& HitResult, const FVector2D& ScreenGazePointUNorm, const FVector& GazeDirection, g2om_raycast& RaycastToModify);
};

View File

@@ -0,0 +1,107 @@
/******************************************************************************
* Copyright 2017- Tobii Technology AB. All rights reserved.
*
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
******************************************************************************/
#pragma once
#include "tobii_g2om.h"
#include "CoreMinimal.h"
static class FTobiiGTOMUtils
{
public:
static g2om_vector UE4VectorToG2OMVector(const FVector& Input)
{
return g2om_vector { Input.X, Input.Y, Input.Z };
}
static FVector G2OMVectorToUE4Vector(const g2om_vector& Input)
{
return FVector(Input.x, Input.y, Input.z);
}
static void UE4SpaceToUnitySpace(g2om_vector& Arg)
{
//Unity forward is Z, right is X, up is Y
//UE4 forward is X, right is Y, up is Z
const float Temp = Arg.z;
Arg.z = Arg.x;
Arg.x = Arg.y;
Arg.y = Temp;
}
static void UnitySpaceToUE4Space(g2om_vector& Arg)
{
//Unity forward is Z, right is X, up is Y
//UE4 forward is X, right is Y, up is Z
const float Temp = Arg.x;
Arg.x = Arg.z;
Arg.z = Arg.y;
Arg.y = Temp;
}
static bool QueryFocusableLayer(UPrimitiveComponent* Candidate, const FName& LayerToQuery)
{
FName FocusLayer = UTobiiGazeFocusableComponent::GetFocusLayerForPrimitive(Candidate);
if (FocusLayer == FName("Default"))
{
return true;
}
return FocusLayer == LayerToQuery;
}
static bool QueryFocusableLayer(UTobiiGazeFocusableWidget* Candidate, const FName& LayerToQuery)
{
FName FocusLayer = UTobiiGazeFocusableComponent::GetFocusLayerForWidget(Candidate);
if (FocusLayer == FName("Default"))
{
return true;
}
return FocusLayer == LayerToQuery;
}
template <class T>
static bool ValidateFocusLayers(const TArray<FName>& FocusLayerFilterList, const bool bIsWhiteList, T* Candidate)
{
if (Candidate == nullptr)
{
return false;
}
if (bIsWhiteList)
{
//A white list only allows focusables that have a layer that is in the list.
for (auto& Layer : FocusLayerFilterList)
{
if (QueryFocusableLayer(Candidate, Layer))
{
return true;
}
}
}
else
{
//A black list will block any focusables whos layer is in the list.
for (auto& Layer : FocusLayerFilterList)
{
if (QueryFocusableLayer(Candidate, Layer))
{
return false;
}
}
//No filters hit, this object is ok.
return true;
}
return false;
}
};
DEFINE_LOG_CATEGORY_STATIC(LogTobiiGTOM, All, All);

View File

@@ -0,0 +1,59 @@
/******************************************************************************
* Copyright 2017- Tobii Technology AB. All rights reserved.
*
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
******************************************************************************/
#include "TobiiGTOMModule.h"
#include "TobiiGazeFocusableComponent.h"
#include "TobiiGazeFocusableWidget.h"
#include "TobiiGTOMBlueprintLibrary.h"
#include "TobiiGTOMInternalTypes.h"
#include "TobiiGTOMEngine.h"
#include "IEyeTracker.h"
#include "DrawDebugHelpers.h"
#include "GameFramework/PlayerController.h"
#include "Misc/Paths.h"
#include "Runtime/Engine/Public/Slate/SceneViewport.h"
IMPLEMENT_MODULE(FTobiiGTOMModule, TobiiGTOM)
void FTobiiGTOMModule::StartupModule()
{
G2OMDllHandle = nullptr;
#if TOBII_EYETRACKING_ACTIVE
FString RelativeG2OMDllPath = FString(TEXT(TOBII_G2OM_RELATIVE_DLL_PATH));
#if TOBII_COMPILE_AS_ENGINE_PLUGIN
FString FullG2OMDllPath = FPaths::Combine(FPaths::EngineDir(), RelativeG2OMDllPath);
#else
FString FullG2OMDllPath = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectPluginsDir(), RelativeG2OMDllPath));
#endif //TOBII_COMPILE_AS_ENGINE_PLUGIN
G2OMDllHandle = FPlatformProcess::GetDllHandle(*FullG2OMDllPath);
if (G2OMDllHandle != nullptr)
{
IModularFeatures::Get().RegisterModularFeature(IInputDeviceModule::GetModularFeatureName(), this);
}
#endif //TOBII_EYETRACKING_ACTIVE
}
void FTobiiGTOMModule::ShutdownModule()
{
#if TOBII_EYETRACKING_ACTIVE
if (G2OMDllHandle != nullptr)
{
IModularFeatures::Get().UnregisterModularFeature(IInputDeviceModule::GetModularFeatureName(), this);
}
G2OMDllHandle = nullptr;
#endif //TOBII_EYETRACKING_ACTIVE
}
TSharedPtr<class IInputDevice> FTobiiGTOMModule::CreateInputDevice(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler)
{
GTOMInputDevice = MakeShareable(new FTobiiGTOMEngine());
return TSharedPtr<class IInputDevice>(GTOMInputDevice);
}

View File

@@ -0,0 +1,35 @@
/******************************************************************************
* Copyright 2017- Tobii Technology AB. All rights reserved.
*
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
******************************************************************************/
#pragma once
#include "CoreMinimal.h"
#include "Runtime/InputDevice/Public/IInputDeviceModule.h"
#include "Modules/ModuleManager.h"
class TOBIIGTOM_API FTobiiGTOMModule : public IInputDeviceModule
{
public:
static inline FTobiiGTOMModule& Get()
{
return FModuleManager::LoadModuleChecked<FTobiiGTOMModule>("TobiiGTOM");
}
static inline bool IsAvailable()
{
return FModuleManager::Get().IsModuleLoaded("TobiiGTOM");
}
public:
TSharedPtr<class FTobiiGTOMEngine> GTOMInputDevice;
virtual void StartupModule() override;
virtual void ShutdownModule() override;
virtual TSharedPtr< class IInputDevice > CreateInputDevice(const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler);
private:
void* G2OMDllHandle;
};

View File

@@ -0,0 +1,203 @@
/******************************************************************************
* Copyright 2017- Tobii Technology AB. All rights reserved.
*
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
******************************************************************************/
#if TOBII_EYETRACKING_ACTIVE
#include "TobiiGTOMOcclusionTester.h"
#include "TobiiGazeFocusableComponent.h"
#include "TobiiGTOMInternalTypes.h"
#include "tobii_g2om.h"
#include "DrawDebugHelpers.h"
#include "Components/PrimitiveComponent.h"
#include "Engine/Engine.h"
#include "Engine/LocalPlayer.h"
#define TOBII_MAX_RAYS_PER_FRAME (15)
static TAutoConsoleVariable<int32> CVarOcclusionTesterTrackingBlastMode(TEXT("tobii.gtom.OcclusionTesterTrackingBlastMode"), (int32)ETobiiTrackingBlastMode::OnlyTrackMissedObjects, TEXT("0 - Do not use tracking blasts. This will be very fast, but quite unstable. 1 - Only do tracking blasts when an object was not hit by a shotgun pellet to reduce hysteres somewhat. Prefer this mode if you have a lot of small objects constantly in the active set and mode two is working too slowly. 2 - Do tracking blasts for every focus component in the active group. This greatly helps reduce hysteres and leads to a LOT more stable last visible locations by trading off performance since it will require an additional extra line cast per active focus object and is such O(n)."));
static TAutoConsoleVariable<float> CVarOcclusionTesterTracesPerSecond(TEXT("tobii.gtom.OcclusionTesterTracesPerSecond"), 700.0f, TEXT("This is the approximate number of additional line casts (in addition to the center one) that will be performed per second. Setting this to zero will make the shotgun behave like a normal line scorer. A higher number will improve reliability at the cost of performance."));
static TAutoConsoleVariable<float> CVarOcclusionTesterTimeToLive(TEXT("tobii.gtom.OcclusionTesterTimeToLive"), 0.5f, TEXT("This is how long an object will be kept in the visibility set given it hasn't been detected. Having this too high can make the occlusion testing system output objects that are no longer visible. Having it too low may lead to hysteres."));
static TAutoConsoleVariable<int32> CVarDebugDisplayGTOMVisibilityRays(TEXT("tobii.debug.DisplayGTOMVisibilityRays"), 1, TEXT("1 means we visualize the rays from GTOM"));
g2om_gaze_ray RaysThisFrame[TOBII_MAX_RAYS_PER_FRAME];
FTobiiGTOMOcclusionTester::FTobiiGTOMOcclusionTester()
{
}
const TMap<FEngineFocusableUID, FTobiiGTOMOcclusionData>& FTobiiGTOMOcclusionTester::Tick(float DeltaTimeSecs, APlayerController* PlayerController, const FEyeTrackerGazeData& GazeData, g2om_gaze_data& G2OMGazeData, g2om_context* G2OMContext)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_TobiiEyetracking_GTOM_OcclusionTesting);
if (GEngine == nullptr
|| GEngine->GameViewport == nullptr
|| PlayerController == nullptr
|| PlayerController->GetWorld() == nullptr)
{
VisibleSet.Empty();
return VisibleSet;
}
static const auto FovealAngleDegCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("tobii.FovealConeAngleDegrees"));
const float FovealAngleDeg = FovealAngleDegCVar->GetFloat();
const ETobiiTrackingBlastMode TrackingBlastMode = (ETobiiTrackingBlastMode)CVarOcclusionTesterTrackingBlastMode.GetValueOnGameThread();
const int32 NrCastsThisFrame = FMath::Clamp(FMath::CeilToInt(CVarOcclusionTesterTracesPerSecond.GetValueOnGameThread() * DeltaTimeSecs), 3, TOBII_MAX_RAYS_PER_FRAME);
const FDateTime UtcNow = FDateTime::UtcNow();
FMemory::Memzero(RaysThisFrame, TOBII_MAX_RAYS_PER_FRAME * sizeof(g2om_gaze_ray));
g2om_get_candidate_search_pattern(G2OMContext, &G2OMGazeData, NrCastsThisFrame, RaysThisFrame);
FCollisionQueryParams CollisionQueryParams;
CollisionQueryParams.AddIgnoredActor(PlayerController);
CollisionQueryParams.AddIgnoredActor(PlayerController->GetPawn());
//Do shotgun blast
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_TobiiEyetracking_GTOM_OcclusionTesting_Shotgun);
for (int32 RayIdx = 0; RayIdx < NrCastsThisFrame; RayIdx++)
{
g2om_gaze_ray& G2OMRay = RaysThisFrame[RayIdx];
if (!G2OMRay.is_valid)
{
continue;
}
FVector CurrentDirection = FTobiiGTOMUtils::G2OMVectorToUE4Vector(G2OMRay.ray.direction);
if (CurrentDirection.Normalize())
{
TestRay(UtcNow, PlayerController, GazeData.GazeOrigin, CurrentDirection, CollisionQueryParams);
}
}
}
//Do tracking rays
if (TrackingBlastMode != ETobiiTrackingBlastMode::NoTrackingBlasts)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_TobiiEyetracking_GTOM_OcclusionTesting_TrackingBlasts);
//First we should check if our previous objects are still visible using so called "tracking blasts".
//This is both to provide more stable last known locations as well as to avoid hysteresis.
for (auto RecordIterator = VisibleSet.CreateIterator(); RecordIterator; ++RecordIterator)
{
const FTobiiGTOMOcclusionData& Record = RecordIterator.Value();
if (TrackingBlastMode == ETobiiTrackingBlastMode::OnlyTrackMissedObjects && Record.LastHitTime == UtcNow)
{
continue;
}
FVector TrackingBlastDirection = Record.LastKnownVisibleWorldSpaceLocation - GazeData.GazeOrigin;
if (TrackingBlastDirection.Normalize())
{
const float DotBetweenBlastDirAndGaze = FVector::DotProduct(GazeData.GazeDirection, TrackingBlastDirection);
const float AngleBetweenObjectAndGazeDeg = FMath::RadiansToDegrees(FMath::Acos(DotBetweenBlastDirAndGaze));
if (AngleBetweenObjectAndGazeDeg > FovealAngleDeg)
{
//We need to clamp the rotation amount.
FVector RotationAxis = FVector::CrossProduct(GazeData.GazeDirection, TrackingBlastDirection);
RotationAxis.Normalize();
TrackingBlastDirection = GazeData.GazeDirection.RotateAngleAxis(FovealAngleDeg, RotationAxis);
}
TestRay(UtcNow, PlayerController, GazeData.GazeOrigin, TrackingBlastDirection, CollisionQueryParams);
}
}
}
//Decay old objects
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_TobiiEyetracking_GTOM_OcclusionTesting_Decay);
TArray<FEngineFocusableUID> DecayedIds;
for (auto RecordIterator = VisibleSet.CreateIterator(); RecordIterator; ++RecordIterator)
{
const FTobiiGTOMOcclusionData& Record = RecordIterator.Value();
if ((UtcNow - Record.LastHitTime).GetTotalSeconds() > CVarOcclusionTesterTimeToLive.GetValueOnGameThread())
{
DecayedIds.Add(RecordIterator.Key());
}
}
for (FEngineFocusableUID DecayID : DecayedIds)
{
VisibleSet.Remove(DecayID);
}
}
return VisibleSet;
}
void FTobiiGTOMOcclusionTester::TestRay(const FDateTime& UtcNow, APlayerController* PlayerController, const FVector& Origin, const FVector& Direction, const FCollisionQueryParams& Params)
{
static const auto DrawDebugCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("tobii.debug"));
const float DebugBoxSize = 2.0f;
static const auto FocusTraceChannelCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("tobii.FocusTraceChannel"));
static const auto MaximumTraceDistanceCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("tobii.MaximumTraceDistance"));
const float MaxTraceDistance = MaximumTraceDistanceCVar->GetFloat();
const ECollisionChannel TraceChannel = (ECollisionChannel)FocusTraceChannelCVar->GetInt();
const FVector RayEndPoint = Origin + (Direction * MaxTraceDistance);
FHitResult RayHitResult;
if (PlayerController->GetWorld()->LineTraceSingleByChannel(RayHitResult, Origin, RayEndPoint, TraceChannel, Params))
{
if (DrawDebugCVar->GetInt() != 0 && CVarDebugDisplayGTOMVisibilityRays.GetValueOnGameThread())
{
DrawDebugBox(PlayerController->GetWorld(), RayHitResult.Location, FVector(DebugBoxSize, DebugBoxSize, DebugBoxSize), FColor::Green, false, 0.0f, 0, 2.0f);
}
if (UTobiiGazeFocusableComponent::IsPrimitiveFocusable(RayHitResult.GetComponent()))
{
//Range test
bool bIsInRange = true;
float MaxDistance;
const float DistanceToPrimitive = FVector::Distance(Origin, RayHitResult.Location);
if (UTobiiGazeFocusableComponent::GetMaxFocusDistanceForPrimitive(RayHitResult.GetComponent(), MaxDistance))
{
bIsInRange = DistanceToPrimitive <= MaxDistance;
}
if (bIsInRange)
{
FEngineFocusableUID Id = RayHitResult.GetComponent()->GetUniqueID();
if (VisibleSet.Contains(Id))
{
VisibleSet[Id].LastKnownVisibleWorldSpaceLocation = RayHitResult.Location;
VisibleSet[Id].LastHitTime = UtcNow;
}
else
{
FTobiiGTOMOcclusionData NewRecord;
NewRecord.ParentActor = RayHitResult.GetActor();
NewRecord.PrimitiveComponent = RayHitResult.GetComponent();
NewRecord.LastKnownVisibleWorldSpaceLocation = RayHitResult.Location;
NewRecord.LastHitTime = UtcNow;
if (NewRecord.ParentActor.IsValid())
{
NewRecord.FocusableComponent = (UTobiiGazeFocusableComponent*)NewRecord.ParentActor->GetComponentByClass(UTobiiGazeFocusableComponent::StaticClass());
}
VisibleSet.Add(Id, MoveTemp(NewRecord));
}
}
}
}
else if (DrawDebugCVar->GetInt() != 0 && CVarDebugDisplayGTOMVisibilityRays.GetValueOnGameThread())
{
DrawDebugBox(PlayerController->GetWorld(), RayEndPoint, FVector(DebugBoxSize, DebugBoxSize, DebugBoxSize), FColor::Red, false, 0.0f, 0, 2.0f);
}
}
#endif //TOBII_EYETRACKING_ACTIVE

View File

@@ -0,0 +1,50 @@
/******************************************************************************
* 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 "TobiiGazeFocusableComponent.h"
#include "tobii_g2om.h"
#include "CoreMinimal.h"
#include "IEyeTracker.h"
#include "GameFramework/PlayerController.h"
enum class ETobiiTrackingBlastMode : uint8
{
NoTrackingBlasts = 0
, OnlyTrackMissedObjects = 1
, TrackAllObjects = 2
};
struct FTobiiGTOMOcclusionData
{
public:
TWeakObjectPtr<AActor> ParentActor;
TWeakObjectPtr<UPrimitiveComponent> PrimitiveComponent;
TWeakObjectPtr<UTobiiGazeFocusableComponent> FocusableComponent;
FVector LastKnownVisibleWorldSpaceLocation;
FDateTime LastHitTime;
};
class FTobiiGTOMOcclusionTester
{
public:
FTobiiGTOMOcclusionTester();
virtual ~FTobiiGTOMOcclusionTester() {}
//Returns the visible set
const TMap<FEngineFocusableUID, FTobiiGTOMOcclusionData>& Tick(float DeltaTimeSecs, APlayerController* PlayerController, const FEyeTrackerGazeData& GazeData, g2om_gaze_data& G2OMGazeData, g2om_context* G2OMContext);
private:
TMap<FEngineFocusableUID, FTobiiGTOMOcclusionData> VisibleSet;
void TestRay(const FDateTime& UtcNow, APlayerController* PlayerController, const FVector& Origin, const FVector& Direction, const FCollisionQueryParams& Params);
};
#endif //TOBII_EYETRACKING_ACTIVE

View File

@@ -0,0 +1,20 @@
/******************************************************************************
* Copyright 2017- Tobii Technology AB. All rights reserved.
*
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
******************************************************************************/
#pragma once
#include "TobiiGTOMTypes.h"
FName FTobiiPrimitiveComponentGazeFocusTags::HasGazeFocusTag("HasGazeFocus");
FName FTobiiPrimitiveComponentGazeFocusTags::GazeFocusableTag("GazeFocusable");
FName FTobiiPrimitiveComponentGazeFocusTags::NotGazeFocusableTag("NotGazeFocusable");
FString FTobiiPrimitiveComponentGazeFocusTags::GazeFocusablePriorityTag("GazeFocusPriority");
FString FTobiiPrimitiveComponentGazeFocusTags::GazeFocusableMaximumDistanceTag("GazeFocusMaxDistance");
FString FTobiiPrimitiveComponentGazeFocusTags::GazeFocusableLayerTag("GazeFocusLayer");
FString FTobiiPrimitiveComponentGazeFocusTags::PrimitiveFocusOffsetXTag("PrimitiveFocusOffsetX");
FString FTobiiPrimitiveComponentGazeFocusTags::PrimitiveFocusOffsetYTag("PrimitiveFocusOffsetY");
FString FTobiiPrimitiveComponentGazeFocusTags::PrimitiveFocusOffsetZTag("PrimitiveFocusOffsetZ");

View File

@@ -0,0 +1,137 @@
/******************************************************************************
* Copyright 2017- Tobii Technology AB. All rights reserved.
*
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
******************************************************************************/
#include "TobiiGazeFocusManagerComponent.h"
#include "TobiiGazeFocusableComponent.h"
#include "TobiiGTOMModule.h"
#include "TobiiGTOMBlueprintLibrary.h"
#include "DrawDebugHelpers.h"
#include "Components/PrimitiveComponent.h"
#include "Engine/Engine.h"
#include "HAL/IConsoleManager.h"
UTobiiGazeFocusManagerComponent::UTobiiGazeFocusManagerComponent()
: bWantPrimitives(true)
, bWantWidgets(true)
, bIsWhiteList(false)
, FocusLayerFilters()
, PreviouslyFocusedPrimitiveComponent()
{
PrimaryComponentTick.bCanEverTick = true;
ResetFocusData();
}
void UTobiiGazeFocusManagerComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
ResetFocusData();
UTobiiGTOMBlueprintLibrary::GetAllFilteredGazeFocusData(FocusLayerFilters, bIsWhiteList, bWantPrimitives, bWantWidgets, AllFocusData);
for (const FTobiiGazeFocusData& FocusData : AllFocusData)
{
if (bWantPrimitives
&& !BestPrimitiveComponentFocusData.FocusedPrimitiveComponent.IsValid()
&& (FocusData.FocusedPrimitiveComponent.IsValid() && !FocusData.FocusedWidget.IsValid()))
{
BestPrimitiveComponentFocusData = FocusData;
}
if (bWantWidgets
&& !BestWidgetFocusData.FocusedWidget.IsValid()
&& FocusData.FocusedWidget.IsValid())
{
BestWidgetFocusData = FocusData;
}
if((!bWantPrimitives || (BestPrimitiveComponentFocusData.FocusedPrimitiveComponent.IsValid() && !BestPrimitiveComponentFocusData.FocusedWidget.IsValid()))
&& (!bWantWidgets || BestWidgetFocusData.FocusedWidget.IsValid()))
{
//We found the best candidates we were after
break;
}
}
//Primitive components
if(bWantPrimitives)
{
TWeakObjectPtr<UPrimitiveComponent> NewTopFocusPrimitive = BestPrimitiveComponentFocusData.FocusedPrimitiveComponent;
if (!NewTopFocusPrimitive.IsValid() && PreviouslyFocusedPrimitiveComponent.IsValid())
{
//We lost focus completely
OnPrimitiveLostGazeFocus.Broadcast(PreviouslyFocusedPrimitiveComponent.Get());
}
else if (NewTopFocusPrimitive.IsValid() && !PreviouslyFocusedPrimitiveComponent.IsValid())
{
//We gained focus from no focus
OnPrimitiveReceivedGazeFocus.Broadcast(NewTopFocusPrimitive.Get());
}
else if (NewTopFocusPrimitive.IsValid() && PreviouslyFocusedPrimitiveComponent.IsValid() && NewTopFocusPrimitive.Get() != PreviouslyFocusedPrimitiveComponent.Get())
{
//Focus shifted from one object to another
OnPrimitiveLostGazeFocus.Broadcast(PreviouslyFocusedPrimitiveComponent.Get());
OnPrimitiveReceivedGazeFocus.Broadcast(NewTopFocusPrimitive.Get());
}
PreviouslyFocusedPrimitiveComponent = NewTopFocusPrimitive;
}
//Widgets
if(bWantWidgets)
{
TWeakObjectPtr<UTobiiGazeFocusableWidget> NewTopFocusWidget = BestPrimitiveComponentFocusData.FocusedWidget;
if (!NewTopFocusWidget.IsValid() && PreviouslyFocusedWidget.IsValid())
{
//We lost focus completely
OnWidgetLostGazeFocus.Broadcast(PreviouslyFocusedWidget.Get());
}
else if (NewTopFocusWidget.IsValid() && !PreviouslyFocusedWidget.IsValid())
{
//We gained focus from no focus
OnWidgetReceivedGazeFocus.Broadcast(NewTopFocusWidget.Get());
}
else if (NewTopFocusWidget.IsValid() && PreviouslyFocusedWidget.IsValid() && NewTopFocusWidget.Get() != PreviouslyFocusedWidget.Get())
{
//Focus shifted from one object to another
OnWidgetLostGazeFocus.Broadcast(PreviouslyFocusedWidget.Get());
OnWidgetReceivedGazeFocus.Broadcast(NewTopFocusWidget.Get());
}
PreviouslyFocusedWidget = NewTopFocusWidget;
}
}
void UTobiiGazeFocusManagerComponent::ResetFocusData()
{
AllFocusData.Empty();
BestPrimitiveComponentFocusData.FocusedPrimitiveComponent.Reset();
BestPrimitiveComponentFocusData.FocusedActor.Reset();
BestPrimitiveComponentFocusData.FocusedWidget.Reset();
BestWidgetFocusData.FocusedPrimitiveComponent.Reset();
BestWidgetFocusData.FocusedActor.Reset();
BestWidgetFocusData.FocusedWidget.Reset();
}
void UTobiiGazeFocusManagerComponent::GetBestFilteredPrimitiveComponentFocusData(FTobiiGazeFocusData& OutFocusData) const
{
OutFocusData = BestPrimitiveComponentFocusData;
}
void UTobiiGazeFocusManagerComponent::GetBestFilteredWidgetComponentFocusData(FTobiiGazeFocusData& OutFocusData) const
{
OutFocusData = BestWidgetFocusData;
}
void UTobiiGazeFocusManagerComponent::GetAllFilteredFocusData(TArray<FTobiiGazeFocusData>& OutFocusData) const
{
OutFocusData = AllFocusData;
}

View File

@@ -0,0 +1,504 @@
/******************************************************************************
* Copyright 2017- Tobii Technology AB. All rights reserved.
*
* @author Temaran | Fredrik Lindh | fredrik.lindh@tobii.com | https://github.com/Temaran
******************************************************************************/
#include "TobiiGazeFocusableComponent.h"
#include "TobiiGTOMBlueprintLibrary.h"
#include "TobiiGTOMModule.h"
#include "DrawDebugHelpers.h"
#include "Engine/Engine.h"
#include "Components/PrimitiveComponent.h"
#include "Components/WidgetComponent.h"
#include "GameFramework/PlayerController.h"
#include "HAL/IConsoleManager.h"
static TAutoConsoleVariable<int32> CVarDrawCleanUIDebug(TEXT("tobii.debug.CleanUI"), 1, TEXT("0 - CleanUI debug visualizations are not displayed. 1 - CleanUI debug visualizations are displayed."));
static TMap<FTobiiFocusableUID, TWeakObjectPtr<UTobiiGazeFocusableComponent>> GRegisteredTobiiFocusableComponents;
static TSet<FPrimitiveComponentId>* GazeFocusPrioSetA = nullptr;
static TSet<FPrimitiveComponentId>* GazeFocusPrioSetB = nullptr;
static bool bUseGazeFocusPrioSetA = true;
void UTobiiGazeFocusableComponent::ClearFocusableComponents()
{
GRegisteredTobiiFocusableComponents.Empty();
}
const TMap<FTobiiFocusableUID, TWeakObjectPtr<UTobiiGazeFocusableComponent>>& UTobiiGazeFocusableComponent::GetFocusableComponents()
{
return GRegisteredTobiiFocusableComponents;
}
void UTobiiGazeFocusableComponent::UpdateGazeFocusPrio(FVector POVActorLocation, int32 MaxNrFocusables)
{
TMap<FPrimitiveComponentId, float> PrioMap;
TSet<FPrimitiveComponentId> NonPrioSet;
//Determine which primitive components should be considered for gaze focus.
for (auto& FocusableElement : GRegisteredTobiiFocusableComponents)
{
if (FocusableElement.Value.IsValid())
{
const UTobiiGazeFocusableComponent* FocusableComponent = FocusableElement.Value.Get();
const AActor* FocusableActor = FocusableComponent->GetOwner();
if (FocusableActor != nullptr)
{
TArray<UActorComponent*> PrimitiveComponents = FocusableActor->GetComponentsByClass(UPrimitiveComponent::StaticClass());
//TArray<UActorComponent*> PrimativeComponents = FocusableActor->GetComponents
for (UActorComponent* Comp : PrimitiveComponents)
{
UPrimitiveComponent* PrimitiveComponent = (UPrimitiveComponent*)Comp;
if (PrimitiveComponent == nullptr)
{
continue;
}
bool bSkipComponent = false;
float MaxDistance = 0.0f;
float Priority = 0.0f;
for (const FName& CurrentTag : PrimitiveComponent->ComponentTags)
{
if (CurrentTag == FTobiiPrimitiveComponentGazeFocusTags::NotGazeFocusableTag)
{
bSkipComponent = true;
break;
}
FString CurrentTagString = CurrentTag.ToString();
if (CurrentTagString.StartsWith(FTobiiPrimitiveComponentGazeFocusTags::GazeFocusableMaximumDistanceTag))
{
FString Argument = CurrentTagString.RightChop(FTobiiPrimitiveComponentGazeFocusTags::GazeFocusableMaximumDistanceTag.Len());
MaxDistance = FCString::Atof(*Argument);
}
if (CurrentTagString.StartsWith(FTobiiPrimitiveComponentGazeFocusTags::GazeFocusablePriorityTag))
{
FString Argument = CurrentTagString.RightChop(FTobiiPrimitiveComponentGazeFocusTags::GazeFocusablePriorityTag.Len());
Priority = FCString::Atof(*Argument);
}
}
if (bSkipComponent)
{
continue;
}
if (MaxDistance == 0.0f)
{
MaxDistance = FocusableComponent->DefaultMaxFocusDistance;
}
if (Priority == 0.0f)
{
Priority = FocusableComponent->DefaultFocusPriority;
}
bool bAcceptComponent = true;
if (MaxDistance != 0.0f)
{
float Distance = FVector::Distance(POVActorLocation, PrimitiveComponent->GetComponentLocation());
if (Distance > MaxDistance)
{
bAcceptComponent = false;
}
}
if (bAcceptComponent)
{
if (Priority == 0.0f)
{
NonPrioSet.Add(PrimitiveComponent->ComponentId);
}
else
{
PrioMap.Add(PrimitiveComponent->ComponentId, Priority);
}
}
}
}
}
}
PrioMap.ValueSort(TGreater<float>());
TSet<FPrimitiveComponentId>* NewGazeFocusPrioSet = new TSet<FPrimitiveComponentId>();
MaxNrFocusables -= 1; //Since we test before add, take that into account
for (auto& SortedElement : PrioMap)
{
if (NewGazeFocusPrioSet->Num() >= MaxNrFocusables)
{
break;
}
NewGazeFocusPrioSet->Add(SortedElement.Key);
}
for (auto NonPrioElement : NonPrioSet)
{
if (NewGazeFocusPrioSet->Num() >= MaxNrFocusables)
{
break;
}
NewGazeFocusPrioSet->Add(NonPrioElement);
}
//Destroy old set, set our new one and flip our record pointer so the one we swapped into becomes the active one.
TSet<FPrimitiveComponentId>*& ListPtr = bUseGazeFocusPrioSetA ? GazeFocusPrioSetB : GazeFocusPrioSetA;
if (ListPtr != nullptr)
{
delete ListPtr;
}
ListPtr = NewGazeFocusPrioSet;
bUseGazeFocusPrioSetA = !bUseGazeFocusPrioSetA;
}
const TSet<FPrimitiveComponentId>* UTobiiGazeFocusableComponent::GetGazeFocusPrioSet()
{
return bUseGazeFocusPrioSetA ? GazeFocusPrioSetA : GazeFocusPrioSetB;
}
bool UTobiiGazeFocusableComponent::IsPrimitiveFocusable(UPrimitiveComponent* Primitive)
{
if (Primitive == nullptr || !Primitive->IsVisible() || Primitive->ComponentHasTag(FTobiiPrimitiveComponentGazeFocusTags::NotGazeFocusableTag))
{
//Override disabled
return false;
}
else if (Primitive->ComponentHasTag(FTobiiPrimitiveComponentGazeFocusTags::GazeFocusableTag))
{
//Override enabled
return true;
}
else if (Primitive->GetOwner())
{
UTobiiGazeFocusableComponent* GazeFocusable = (UTobiiGazeFocusableComponent*)Primitive->GetOwner()->GetComponentByClass(UTobiiGazeFocusableComponent::StaticClass());
if (GazeFocusable != nullptr && GazeFocusable->bDefaultIsFocusable)
{
return true;
}
}
return false;
}
bool UTobiiGazeFocusableComponent::GetMaxFocusDistanceForPrimitive(UPrimitiveComponent* Primitive, float& OutMaxDistance)
{
if (Primitive == nullptr)
{
return false;
}
bool bMaxDistanceFound = false;
for (const FName& CurrentTag : Primitive->ComponentTags)
{
FString CurrentTagString = CurrentTag.ToString();
if (CurrentTagString.StartsWith(FTobiiPrimitiveComponentGazeFocusTags::GazeFocusableMaximumDistanceTag))
{
FString Argument = CurrentTagString.RightChop(FTobiiPrimitiveComponentGazeFocusTags::GazeFocusableMaximumDistanceTag.Len());
OutMaxDistance = FCString::Atof(*Argument);
bMaxDistanceFound = OutMaxDistance != 0.0f; //Atof returns 0.0 on failure
}
}
if (!bMaxDistanceFound && Primitive->GetOwner())
{
UTobiiGazeFocusableComponent* GazeFocusableComponent = (UTobiiGazeFocusableComponent*)Primitive->GetOwner()->GetComponentByClass(UTobiiGazeFocusableComponent::StaticClass());
if (GazeFocusableComponent != nullptr && GazeFocusableComponent->DefaultMaxFocusDistance > FLT_EPSILON)
{
OutMaxDistance = GazeFocusableComponent->DefaultMaxFocusDistance;
bMaxDistanceFound = true;
}
}
return bMaxDistanceFound;
}
float UTobiiGazeFocusableComponent::GetFocusPriorityForPrimitive(UPrimitiveComponent* Primitive)
{
float Priority = 0.0f;
bool bPriorityFound = false;
if (Primitive == nullptr)
{
return Priority;
}
for (const FName& CurrentTag : Primitive->ComponentTags)
{
FString CurrentTagString = CurrentTag.ToString();
if (CurrentTagString.StartsWith(FTobiiPrimitiveComponentGazeFocusTags::GazeFocusablePriorityTag))
{
FString Argument = CurrentTagString.RightChop(FTobiiPrimitiveComponentGazeFocusTags::GazeFocusablePriorityTag.Len());
Priority = FCString::Atof(*Argument);
bPriorityFound = Priority != 0.0f; //Atof returns 0.0 on failure
}
}
if (!bPriorityFound && Primitive->GetOwner())
{
UTobiiGazeFocusableComponent* GazeFocusableComponent = (UTobiiGazeFocusableComponent*)Primitive->GetOwner()->GetComponentByClass(UTobiiGazeFocusableComponent::StaticClass());
if (GazeFocusableComponent != nullptr)
{
Priority = GazeFocusableComponent->DefaultFocusPriority;
}
}
return Priority;
}
FName UTobiiGazeFocusableComponent::GetFocusLayerForPrimitive(UPrimitiveComponent* Primitive)
{
FName FocusLayer = FName("Default");
bool bFocusLayerFound = false;
if (Primitive == nullptr)
{
return FocusLayer;
}
for (const FName& CurrentTag : Primitive->ComponentTags)
{
FString CurrentTagString = CurrentTag.ToString();
if (CurrentTagString.StartsWith(FTobiiPrimitiveComponentGazeFocusTags::GazeFocusableLayerTag))
{
FString Argument = CurrentTagString.RightChop(FTobiiPrimitiveComponentGazeFocusTags::GazeFocusableLayerTag.Len());
Argument.TrimStartAndEndInline();
if (!Argument.IsEmpty())
{
FocusLayer = FName(*Argument);
bFocusLayerFound = true;
}
}
}
if (!bFocusLayerFound && Primitive->GetOwner())
{
UTobiiGazeFocusableComponent* GazeFocusableComponent = (UTobiiGazeFocusableComponent*)Primitive->GetOwner()->GetComponentByClass(UTobiiGazeFocusableComponent::StaticClass());
if (GazeFocusableComponent != nullptr)
{
FocusLayer = GazeFocusableComponent->DefaultFocusLayer;
}
}
return FocusLayer;
}
bool UTobiiGazeFocusableComponent::IsWidgetFocusable(UTobiiGazeFocusableWidget* GazeFocusableWidget)
{
if (GazeFocusableWidget == nullptr || !GazeFocusableWidget->IsVisible() || !GazeFocusableWidget->bIsGazeFocusable)
{
return false;
}
else if (GazeFocusableWidget->GetWorldSpaceHostWidgetComponent() != nullptr && GazeFocusableWidget->GetWorldSpaceHostWidgetComponent()->GetOwner() != nullptr)
{
if (!GazeFocusableWidget->GetWorldSpaceHostWidgetComponent()->IsVisible())
{
return false;
}
UTobiiGazeFocusableComponent* GazeFocusable = (UTobiiGazeFocusableComponent*)GazeFocusableWidget->GetWorldSpaceHostWidgetComponent()->GetOwner()->GetComponentByClass(UTobiiGazeFocusableComponent::StaticClass());
if (GazeFocusable != nullptr)
{
return GazeFocusable->bDefaultIsFocusable;
}
}
return true;
}
bool UTobiiGazeFocusableComponent::GetMaxFocusDistanceForWidget(UTobiiGazeFocusableWidget* GazeFocusableWidget, float& OutMaxDistance)
{
if (GazeFocusableWidget == nullptr)
{
return false;
}
bool bMaxDistanceFound = false;
if (GazeFocusableWidget->MaxFocusDistance >= FLT_EPSILON)
{
OutMaxDistance = GazeFocusableWidget->MaxFocusDistance;
bMaxDistanceFound = true;
}
else if (GazeFocusableWidget->GetWorldSpaceHostWidgetComponent() != nullptr && GazeFocusableWidget->GetWorldSpaceHostWidgetComponent()->GetOwner() != nullptr)
{
UTobiiGazeFocusableComponent* GazeFocusableComponent = (UTobiiGazeFocusableComponent*)GazeFocusableWidget->GetWorldSpaceHostWidgetComponent()->GetOwner()->GetComponentByClass(UTobiiGazeFocusableComponent::StaticClass());
if (GazeFocusableComponent != nullptr && GazeFocusableComponent->DefaultMaxFocusDistance > FLT_EPSILON)
{
OutMaxDistance = GazeFocusableComponent->DefaultMaxFocusDistance;
bMaxDistanceFound = true;
}
}
return bMaxDistanceFound;
}
float UTobiiGazeFocusableComponent::GetFocusPriorityForWidget(UTobiiGazeFocusableWidget* GazeFocusableWidget)
{
if (GazeFocusableWidget == nullptr)
{
return 0.0f;
}
if (GazeFocusableWidget->GazeFocusPriority >= 0.0f)
{
return GazeFocusableWidget->GazeFocusPriority;
}
else if (GazeFocusableWidget->GetWorldSpaceHostWidgetComponent() != nullptr && GazeFocusableWidget->GetWorldSpaceHostWidgetComponent()->GetOwner() != nullptr)
{
UTobiiGazeFocusableComponent* GazeFocusableComponent = (UTobiiGazeFocusableComponent*)GazeFocusableWidget->GetWorldSpaceHostWidgetComponent()->GetOwner()->GetComponentByClass(UTobiiGazeFocusableComponent::StaticClass());
if (GazeFocusableComponent)
{
return GazeFocusableComponent->DefaultFocusPriority;
}
}
return 0.0f;
}
FName UTobiiGazeFocusableComponent::GetFocusLayerForWidget(UTobiiGazeFocusableWidget* GazeFocusableWidget)
{
FName FocusLayer = FName("Default");
if (GazeFocusableWidget == nullptr)
{
return FocusLayer;
}
if (!GazeFocusableWidget->FocusLayer.IsEmpty())
{
FocusLayer = FName(*GazeFocusableWidget->FocusLayer);
}
else if (GazeFocusableWidget->GetWorldSpaceHostWidgetComponent() != nullptr && GazeFocusableWidget->GetWorldSpaceHostWidgetComponent()->GetOwner() != nullptr)
{
UTobiiGazeFocusableComponent* GazeFocusableComponent = (UTobiiGazeFocusableComponent*)GazeFocusableWidget->GetWorldSpaceHostWidgetComponent()->GetOwner()->GetComponentByClass(UTobiiGazeFocusableComponent::StaticClass());
if (GazeFocusableComponent != nullptr)
{
FocusLayer = GazeFocusableComponent->DefaultFocusLayer;
}
}
return FocusLayer;
}
//////////////////////////////////////////////
UTobiiGazeFocusableComponent::UTobiiGazeFocusableComponent()
: bDefaultIsFocusable(true)
, DefaultFocusPriority(0.0f)
, DefaultMaxFocusDistance(0.0f)
, DefaultFocusLayer("Default")
, bWidgetsRefreshedOnce(false)
{
PrimaryComponentTick.bCanEverTick = true;
}
void UTobiiGazeFocusableComponent::PrimitiveReceivedGazeFocus(UPrimitiveComponent* FocusedComponent)
{
OnPrimitiveReceivedGazeFocus.Broadcast(FocusedComponent);
}
void UTobiiGazeFocusableComponent::PrimitiveLostGazeFocus(UPrimitiveComponent* FocusedComponent)
{
OnPrimitiveLostGazeFocus.Broadcast(FocusedComponent);
}
void UTobiiGazeFocusableComponent::WidgetReceivedGazeFocus( UTobiiGazeFocusableWidget* FocusedWidget)
{
OnWidgetReceivedGazeFocus.Broadcast(FocusedWidget);
}
void UTobiiGazeFocusableComponent::WidgetLostGazeFocus(UTobiiGazeFocusableWidget* FocusedWidget)
{
OnWidgetLostGazeFocus.Broadcast(FocusedWidget);
}
const TArray<TWeakObjectPtr<UTobiiGazeFocusableWidget>>& UTobiiGazeFocusableComponent::GetFocusableWidgetsForPrimitiveComponent(UPrimitiveComponent* PrimitiveComponent)
{
if (PrimitiveComponent == nullptr || !PrimitiveMap.Contains(PrimitiveComponent->GetUniqueID()))
{
static TArray<TWeakObjectPtr<UTobiiGazeFocusableWidget>> DefaultArray;
return DefaultArray;
}
return PrimitiveMap[PrimitiveComponent->GetUniqueID()];
}
void UTobiiGazeFocusableComponent::BeginPlay()
{
Super::BeginPlay();
bWidgetsRefreshedOnce = false;
GRegisteredTobiiFocusableComponents.Add(GetUniqueID(), this);
}
void UTobiiGazeFocusableComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
GRegisteredTobiiFocusableComponents.Remove(GetUniqueID());
Super::EndPlay(EndPlayReason);
}
void UTobiiGazeFocusableComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (GEngine == nullptr
|| GEngine->GameViewport == nullptr
|| GEngine->GameViewport->GetGameViewport() == nullptr)
{
return;
}
if (!bWidgetsRefreshedOnce)
{
bWidgetsRefreshedOnce = true;
RefreshOwnedWidgets();
}
}
void UTobiiGazeFocusableComponent::RefreshOwnedWidgets()
{
AllFocusableWidgets.Empty();
PrimitiveMap.Empty();
AActor* Owner = GetOwner();
if (Owner != nullptr)
{
TArray<UActorComponent*> WidgetComponents = Owner->GetComponentsByClass(UWidgetComponent::StaticClass());
for (UActorComponent* Component : WidgetComponents)
{
UWidgetComponent* WidgetComponent = (UWidgetComponent*)Component;
TArray<TWeakObjectPtr<UTobiiGazeFocusableWidget>>& NewFocusableArray = PrimitiveMap.Add(WidgetComponent->GetUniqueID(), TArray<TWeakObjectPtr<UTobiiGazeFocusableWidget>>());
GatherGazeFocusableWidgets(WidgetComponent->GetUserWidgetObject(), NewFocusableArray, WidgetComponent);
}
}
}
void UTobiiGazeFocusableComponent::GatherGazeFocusableWidgets(UWidget* Parent, TArray<TWeakObjectPtr<UTobiiGazeFocusableWidget>>& WidgetArray, UWidgetComponent* OptionalHostWidgetComponent)
{
UUserWidget* UserWidget = Cast<UUserWidget>(Parent);
if (UserWidget != nullptr)
{
GatherGazeFocusableWidgets(UserWidget->GetRootWidget(), WidgetArray, OptionalHostWidgetComponent);
}
else
{
UTobiiGazeFocusableWidget* CleanUIContainer = Cast<UTobiiGazeFocusableWidget>(Parent);
if (CleanUIContainer != nullptr)
{
CleanUIContainer->RegisterWidgetToGTOM(OptionalHostWidgetComponent);
AllFocusableWidgets.Add(CleanUIContainer->GetUniqueID(), CleanUIContainer);
WidgetArray.Add(CleanUIContainer);
}
UPanelWidget* Panel = Cast<UPanelWidget>(Parent);
if (Panel != nullptr)
{
for (int32 ChildIdx = 0; ChildIdx < Panel->GetChildrenCount(); ChildIdx++)
{
GatherGazeFocusableWidgets(Panel->GetChildAt(ChildIdx), WidgetArray, OptionalHostWidgetComponent);
}
}
}
}