Adding Json Mapping for Gestures

This commit is contained in:
DuOtto
2026-04-28 15:04:20 +02:00
parent c7c37b339c
commit d53d270dee
6 changed files with 229 additions and 101 deletions
@@ -9,6 +9,104 @@ float UGestureDefinition::Evaluate(AHandActor* Hand) const
return 0.f;
}
int32 UGestureDefinition::GetFingerTipIndex(EHandFinger Finger) const
{
switch (Finger)
{
case EHandFinger::Thumb: return 4;
case EHandFinger::Index: return 8;
case EHandFinger::Middle: return 12;
case EHandFinger::Ring: return 16;
case EHandFinger::Pinky: return 20;
}
return 0;
}
int32 UGestureDefinition::GetFingerBaseIndex(EHandFinger Finger) const
{
switch (Finger)
{
case EHandFinger::Thumb: return 1; // thumb base is special
case EHandFinger::Index: return 5;
case EHandFinger::Middle: return 9;
case EHandFinger::Ring: return 13;
case EHandFinger::Pinky: return 17;
}
return 0;
}
const FVector& UGestureDefinition::GetPoint(const AHandActor* Hand, int32 Index) const
{
return Hand->Points[Index];
}
float UGestureDefinition::GetHandScale(const AHandActor* Hand) const
{
const TArray<FVector>& P = Hand->Points;
return FVector::Dist(P[0], P[9]); // wrist -> middle base
}
float UGestureDefinition::GetNormalizedDistance(const AHandActor* Hand, int32 A, int32 B) const
{
float Dist = FVector::Dist(GetPoint(Hand, A), GetPoint(Hand, B));
return Dist / GetHandScale(Hand);
}
float UGestureDefinition::GetPinchScore(const AHandActor* Hand, EHandFinger A, EHandFinger B) const
{
float Dist = GetNormalizedDistance(
Hand,
GetFingerTipIndex(A),
GetFingerTipIndex(B)
);
const float Threshold = 0.25f;
return 1.0f - FMath::Clamp(Dist / Threshold, 0.f, 1.f);
}
float UGestureDefinition::GetFingerExtendedScore(const AHandActor* Hand, EHandFinger Finger) const
{
const TArray<FVector>& P = Hand->Points;
const FVector& Wrist = P[0];
const FVector& Base = P[GetFingerBaseIndex(Finger)];
const FVector& Tip = P[GetFingerTipIndex(Finger)];
float Dot = FVector::DotProduct(
(Tip - Base).GetSafeNormal(),
(Base - Wrist).GetSafeNormal()
);
return FMath::Clamp(Dot, 0.f, 1.f);
}
float UGestureDefinition::GetFingerCurledScore(const AHandActor* Hand, EHandFinger Finger) const
{
return 1.0f - GetFingerExtendedScore(Hand, Finger);
}
float UGestureDefinition::GetFingersAverage(const AHandActor* Hand, const TArray<EHandFinger>& Fingers, bool bExtended) const
{
float Sum = 0.f;
for (EHandFinger Finger : Fingers)
{
Sum += bExtended
? GetFingerExtendedScore(Hand, Finger)
: GetFingerCurledScore(Hand, Finger);
}
return Fingers.Num() > 0 ? Sum / Fingers.Num() : 0.f;
}
FVector UGestureDefinition::GetFingerDirection(const AHandActor* Hand, EHandFinger Finger) const
{
const FVector& Base = GetPoint(Hand, GetFingerBaseIndex(Finger));
const FVector& Tip = GetPoint(Hand, GetFingerTipIndex(Finger));
return (Tip - Base).GetSafeNormal();
}
float UGesture_PinchPoint::Evaluate(AHandActor* Hand) const
{
float Pinch = GetPinchScore(Hand, EHandFinger::Thumb, EHandFinger::Index);
@@ -25,6 +25,8 @@ void UGestureManagerComponent::BeginPlay()
HandManager->OnHandAdded.AddDynamic(this, &UGestureManagerComponent::OnHandAdded);
HandManager->OnHandRemoved.AddDynamic(this, &UGestureManagerComponent::OnHandRemoved);
LoadBindings("C:/git/Table/Test/Gestures/gesture_config.json");
}
void UGestureManagerComponent::EvaluateHands()
@@ -64,6 +66,107 @@ void UGestureManagerComponent::OnHandRemoved(AHandActor* Hand)
Hand->ResetGesture();
}
void UGestureManagerComponent::LoadBindings(const FString& FilePath)
{
FString JsonString;
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("bindings"), BindingsArray))
{
for (const TSharedPtr<FJsonValue>& Value : *BindingsArray)
{
TSharedPtr<FJsonObject> Obj = Value->AsObject();
if (!Obj.IsValid()) continue;
FGestureBinding Binding;
Obj->TryGetStringField(TEXT("action"), Binding.ActionName);
Obj->TryGetStringField(TEXT("left"), Binding.LeftGesture);
Obj->TryGetStringField(TEXT("right"), Binding.RightGesture);
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"), Ext.GestureName);
ExtObj->TryGetStringField(TEXT("action"), Ext.GestureAction);
Binding.ExentdedGestures.Add(Ext);
}
}
GestureBindings.Add(Binding);
}
}
}
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);
}
}
void UGestureManagerComponent::BuildActionMap()
{
if (!GestureActionTable) return;
static const FString Context = TEXT("GestureActionContext");
TArray<FGestureActionRow*> Rows;
GestureActionTable->GetAllRows(Context, Rows);
for (FGestureActionRow* Row : Rows)
{
if (!Row || !Row->ActionClass) continue;
ActionClassMap.Add(
Row->ActionClass->GetName(),
Row->ActionClass
);
}
}
void UGestureManagerComponent::CheckGestureDefinitions(TArray<AHandActor*>& Hands)
{
const float MinScoreThreshold = 0.5f;
@@ -10,6 +10,7 @@ AHandManager::AHandManager()
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
GestureManager = CreateDefaultSubobject<UGestureManagerComponent>(TEXT("GestureManager"));
GestureManager->Initialize(GestureDefinitionTable, GestureActionTable);
}
// Called when the game starts or when spawned