451 lines
12 KiB
C++
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;
|
|
}
|
|
|