Files
Table-UE/Table/Source/Table/Private/GestureManagerComponent.cpp
T

451 lines
12 KiB
C++

// Fill out your copyright notice in the Description page of Project Settings.
#include "GestureManagerComponent.h"
#include "HandManager.h"
#include "GestureDefinition.h"
#include "GestureAction.h"
#include "HandActor.h"
// Sets default values for this component's properties
UGestureManagerComponent::UGestureManagerComponent()
{
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = false;
}
// Called when the game starts
void UGestureManagerComponent::BeginPlay()
{
Super::BeginPlay();
HandManager = Cast<AHandManager>(GetOwner());
HandManager->OnHandAdded.AddDynamic(this, &UGestureManagerComponent::OnHandAdded);
HandManager->OnHandRemoved.AddDynamic(this, &UGestureManagerComponent::OnHandRemoved);
LoadBindings("C:/git/Table/Test/Gestures/gesture_config.json");
}
void UGestureManagerComponent::Initialize(UDataTable* InGestureTable, UDataTable* InActionTable)
{
GestureDefinitionTable = InGestureTable;
GestureActionTable = InActionTable;
BuildDefinitionMap();
BuildActionMap();
}
void UGestureManagerComponent::BuildDefinitionMap()
{
if (!GestureDefinitionTable) return;
static const FString Context = TEXT("GestureDefinitionContext");
TArray<FGestureDefinitionRow*> Rows;
GestureDefinitionTable->GetAllRows(Context, Rows);
for (FGestureDefinitionRow* Row : Rows)
{
if (!Row || !Row->DefinitionClass) continue;
UGestureDefinition* Def = NewObject<UGestureDefinition>(this, Row->DefinitionClass);
DefinitionMap.Add(Def->GestureName, Def);
GestureDefinitions.Add(Def);
}
}
void UGestureManagerComponent::BuildActionMap()
{
if (!GestureActionTable) return;
static const FString Context = TEXT("GestureActionContext");
TArray<FName> RowNames = GestureActionTable->GetRowNames();
for (const FName& RowName : RowNames)
{
FGestureActionRow* Row = GestureActionTable->FindRow<FGestureActionRow>( RowName, Context);
if (!Row || !Row->ActionClass) continue;
ActionClassMap.Add(RowName, Row->ActionClass);
}
}
void UGestureManagerComponent::EvaluateHands()
{
TArray<AHandActor*>& Hands = HandManager->HandList;
for (AHandActor* Hand : Hands) Hand->bUsedThisTick = false;
CheckGestureDefinitions(Hands);
for (AHandActor* Hand : Hands)
{
if (Hand->bUsedThisTick) continue;
switch (Hand->Phase)
{
case EHandGesturePhase::Started:
TriggerAction(Hand);
break;
case EHandGesturePhase::Ended:
Hand->ResetGesture();
break;
}
}
UpdateOngoingActions();
}
void UGestureManagerComponent::OnHandAdded(AHandActor* Hand)
{
Hand->ResetGesture();
}
void UGestureManagerComponent::OnHandRemoved(AHandActor* Hand)
{
if (!Hand->ActiveAction) return;
if (!ActiveGestureActions.Contains(Hand->ActiveAction)) return;
Hand->ActiveAction->Destroy();
ActiveGestureActions.Remove(Hand->ActiveAction);
}
void UGestureManagerComponent::LoadBindings(const FString& FilePath)
{
FString JsonString = "";
FString Temp = "";
if (!FFileHelper::LoadFileToString(JsonString, *FilePath))
{
return;
}
TSharedPtr<FJsonObject> Root;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
if (!FJsonSerializer::Deserialize(Reader, Root) || !Root.IsValid())
return;
const TArray<TSharedPtr<FJsonValue>>* BindingsArray;
if (Root->TryGetArrayField(TEXT("rules"), BindingsArray))
{
for (const TSharedPtr<FJsonValue>& Value : *BindingsArray)
{
TSharedPtr<FJsonObject> Obj = Value->AsObject();
if (!Obj.IsValid()) continue;
FGestureBinding Binding;
Obj->TryGetStringField(TEXT("action"), Temp);
Binding.ActionName = FName(Temp);
Obj->TryGetStringField(TEXT("left"), Temp);
Binding.LeftGesture = FName(Temp);
Obj->TryGetStringField(TEXT("right"), Temp);
Binding.RightGesture = FName(Temp);
const TArray<TSharedPtr<FJsonValue>>* ExtArray;
if (Obj->TryGetArrayField(TEXT("extensions"), ExtArray))
{
for (const TSharedPtr<FJsonValue>& ExtVal : *ExtArray)
{
TSharedPtr<FJsonObject> ExtObj = ExtVal->AsObject();
if (!ExtObj.IsValid()) continue;
FExtendedGestureBinding Ext;
ExtObj->TryGetStringField(TEXT("gesture"), Temp);
Ext.GestureName = FName(Temp);
ExtObj->TryGetStringField(TEXT("action"), Temp);
Ext.GestureAction = FName(Temp);
Binding.ExtendedGestures.Add(Ext);
}
}
GestureBindings.Add(Binding);
}
}
}
void UGestureManagerComponent::CheckGestureDefinitions(TArray<AHandActor*>& Hands)
{
//const float MinScoreThreshold = 0.7f;
for (AHandActor* Hand : Hands) {
float Threshold = (Hand->Phase == EHandGesturePhase::Ongoing || Hand->Phase == EHandGesturePhase::Started) ? 0.60f : 0.75f;
float BestScore = 0.f;
FName BestGesture = "";
for (UGestureDefinition* Gesture : GestureDefinitions) {
float Score = Gesture->Evaluate(Hand); // Getting the Score of the gestures to check
//UE_LOG(LogTemp, Warning, TEXT("%s -> %s : %.3f"), *Hand->GetName(), *Gesture->GestureName, Score);
if (Score > BestScore) // Getting the gesture with the highest Score
{
BestScore = Score;
BestGesture = Gesture->GestureName;
}
}
// Check if the gesture with the highest score is a valid Gesture
if (BestScore < Threshold)
{
if (Hand->Phase != EHandGesturePhase::None)
{
Hand->FailFrames++;
UE_LOG(LogTemp, Warning,
TEXT("[%s] Below threshold (%s) Score=%.3f Fail=%d/%d"),
*Hand->GetName(),
*Hand->ActiveGestureName.ToString(),
BestScore,
Hand->FailFrames,
FailThreshold);
//if (Hand->FailFrames >= FailThreshold) Hand->Phase = EHandGesturePhase::Ended;
if (Hand->FailFrames >= FailThreshold)
{
UE_LOG(LogTemp, Warning,
TEXT("[%s] -> ENDED (%s)"),
*Hand->GetName(),
*Hand->ActiveGestureName.ToString());
Hand->Phase = EHandGesturePhase::Ended;
}
}
continue;
}
// Handle valid Gesture
switch (Hand->Phase)
{
case EHandGesturePhase::None: // Starting a Candidate
Hand->ActiveGestureName = BestGesture;
Hand->Phase = EHandGesturePhase::Candidate;
Hand->CandidateFrames = 1;
Hand->FailFrames = 0;
UE_LOG(LogTemp, Warning,
TEXT("[%s] NONE -> CANDIDATE (%s)"),
*Hand->GetName(),
*BestGesture.ToString());
break;
case EHandGesturePhase::Candidate: // Checking if the Candidate stays valid or another gesture is made
if (Hand->ActiveGestureName == BestGesture)
{
Hand->CandidateFrames++;
Hand->FailFrames = 0;
UE_LOG(LogTemp, Warning,
TEXT("[%s] Candidate %s (%d/%d)"),
*Hand->GetName(),
*BestGesture.ToString(),
Hand->CandidateFrames,
CandidateThreshold);
//if (Hand->CandidateFrames >= CandidateThreshold) Hand->Phase = EHandGesturePhase::Started;
if (Hand->CandidateFrames >= CandidateThreshold)
{
Hand->Phase = EHandGesturePhase::Started;
UE_LOG(LogTemp, Warning,
TEXT("[%s] CANDIDATE -> STARTED (%s)"),
*Hand->GetName(),
*BestGesture.ToString());
}
break;
}
UE_LOG(LogTemp, Warning,
TEXT("[%s] Candidate switched %s -> %s"),
*Hand->GetName(),
*Hand->ActiveGestureName.ToString(),
*BestGesture.ToString());
Hand->ActiveGestureName = BestGesture;
Hand->CandidateFrames = 1;
Hand->FailFrames = 0;
break;
case EHandGesturePhase::Ongoing: // Checking if the gesture is still ongoinga
//if (Hand->ActiveGestureName != BestGesture) Hand->FailFrames++;
//else Hand->FailFrames = 0;
if (Hand->ActiveGestureName != BestGesture)
{
Hand->FailFrames++;
UE_LOG(LogTemp, Warning,
TEXT("[%s] Ongoing fail %s (%d/%d)"),
*Hand->GetName(),
*Hand->ActiveGestureName.ToString(),
Hand->FailFrames,
FailThreshold);
}
else
{
if (Hand->FailFrames > 0)
{
UE_LOG(LogTemp, Warning,
TEXT("[%s] Ongoing recovered %s"),
*Hand->GetName(),
*Hand->ActiveGestureName.ToString());
}
}
break;
//case EHandGesturePhase::Started:
// break;
default: // If the gesture would be stuck somewhere
if (Hand->ActiveGestureName != BestGesture) Hand->FailFrames++;
if (Hand->FailFrames >= FailThreshold) Hand->ResetGesture(); // This optimaly shouldnt be called here
break;
}
// Checking if a gesture has ended
if (Hand->FailFrames >= FailThreshold) Hand->Phase = EHandGesturePhase::Ended;
}
}
void UGestureManagerComponent::TriggerAction(AHandActor* Hand)
{
if (Hand->bUsedThisTick) return;
if (TryExtendGestureAction(Hand)) return;
for (const FGestureBinding& Binding : GestureBindings)
{
bool bMatches = (Hand->Handedness == EHandedness::Left && Binding.LeftGesture == Hand->ActiveGestureName)
|| (Hand->Handedness == EHandedness::Right && Binding.RightGesture == Hand->ActiveGestureName);
if (!bMatches) continue;
AGestureAction* NewAction = SpawnGestureAction(Binding.ActionName, Hand);
if (!NewAction) return;
NewAction->InitAction(Hand, Binding, UEUnitsPerUnit, TableHeight);
Hand->Phase = EHandGesturePhase::Ongoing;
return;
}
}
void UGestureManagerComponent::UpdateOngoingActions()
{
for (AGestureAction* Action : ActiveGestureActions)
{
if (!Action) continue;
Action->UpdateAction();
}
}
AGestureAction* UGestureManagerComponent::SpawnGestureAction(FName ActionName, AHandActor* Hand)
{
TSubclassOf<AGestureAction>* ActionClassPtr = ActionClassMap.Find(ActionName);
if (!ActionClassPtr || !Hand) return nullptr;
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = GetOwner();
SpawnParams.SpawnCollisionHandlingOverride =
ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
AGestureAction* NewAction = GetWorld()->SpawnActor<AGestureAction>(
*ActionClassPtr,
FVector::ZeroVector,
FRotator::ZeroRotator,
SpawnParams);
if (!NewAction) return nullptr;
ActiveGestureActions.Add(NewAction);
Hand->ActiveAction = NewAction;
Hand->bUsedThisTick = true;
Hand->Phase = EHandGesturePhase::Ongoing;
return NewAction;
}
bool UGestureManagerComponent::TryExtendGestureAction(AHandActor* Hand)
{
if (!Hand->IsPaired() || Hand->PairedHand->Phase != EHandGesturePhase::Ongoing || !Hand->PairedHand->ActiveAction) return false;
if (TryJoinExistingAction(Hand)) return true;
if (TryCreateExtension(Hand)) return true;
return false;
}
bool UGestureManagerComponent::TryJoinExistingAction(AHandActor* Hand)
{
AHandActor* OtherHand = Hand->PairedHand;
AGestureAction* OtherAction = OtherHand->ActiveAction;
if (!OtherAction->bRequiresSecondHand) return false; // Check if its a Two Handed Action or an extension
FGestureBinding& Binding = OtherAction->Binding;
bool bMatches = (Hand->Handedness == EHandedness::Left && Binding.LeftGesture == Hand->ActiveGestureName)
|| (Hand->Handedness == EHandedness::Right && Binding.RightGesture == Hand->ActiveGestureName);
if (!bMatches) return false;
OtherAction->AddOtherHand(Hand);
OtherHand->bUsedThisTick = true;
Hand->bUsedThisTick = true;
Hand->Phase = EHandGesturePhase::Ongoing;
Hand->ActiveAction = OtherAction;
return true;
}
bool UGestureManagerComponent::TryCreateExtension(AHandActor* Hand)
{
AHandActor* OtherHand = Hand->PairedHand;
AGestureAction* OtherAction = OtherHand->ActiveAction;
if (!OtherHand->ActiveAction->bExtendable) return false;
FName ActionName;
for (const FExtendedGestureBinding& Gesture : OtherAction->Binding.ExtendedGestures)
{
if (Gesture.GestureName == Hand->ActiveGestureName)
{
ActionName = Gesture.GestureAction;
break;
}
}
if (ActionName.IsNone()) return false;
AGestureAction* NewAction = SpawnGestureAction(ActionName, Hand);
if (!NewAction) return false;
ActiveGestureActions.Add(NewAction);
NewAction->InitAction(Hand, OtherHand->ActiveAction->Binding, UEUnitsPerUnit, TableHeight);
OtherHand->bUsedThisTick = true;
Hand->bUsedThisTick = true;
OtherAction->AddExtension(NewAction);
NewAction->AddExtension(OtherHand->ActiveAction);
return true;
}