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; 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 UGesture_PinchPoint::Evaluate(AHandActor* Hand) const
{ {
float Pinch = GetPinchScore(Hand, EHandFinger::Thumb, EHandFinger::Index); float Pinch = GetPinchScore(Hand, EHandFinger::Thumb, EHandFinger::Index);
@@ -25,6 +25,8 @@ void UGestureManagerComponent::BeginPlay()
HandManager->OnHandAdded.AddDynamic(this, &UGestureManagerComponent::OnHandAdded); HandManager->OnHandAdded.AddDynamic(this, &UGestureManagerComponent::OnHandAdded);
HandManager->OnHandRemoved.AddDynamic(this, &UGestureManagerComponent::OnHandRemoved); HandManager->OnHandRemoved.AddDynamic(this, &UGestureManagerComponent::OnHandRemoved);
LoadBindings("C:/git/Table/Test/Gestures/gesture_config.json");
} }
void UGestureManagerComponent::EvaluateHands() void UGestureManagerComponent::EvaluateHands()
@@ -64,6 +66,107 @@ void UGestureManagerComponent::OnHandRemoved(AHandActor* Hand)
Hand->ResetGesture(); 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) void UGestureManagerComponent::CheckGestureDefinitions(TArray<AHandActor*>& Hands)
{ {
const float MinScoreThreshold = 0.5f; 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. // 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; PrimaryActorTick.bCanEverTick = true;
GestureManager = CreateDefaultSubobject<UGestureManagerComponent>(TEXT("GestureManager")); GestureManager = CreateDefaultSubobject<UGestureManagerComponent>(TEXT("GestureManager"));
GestureManager->Initialize(GestureDefinitionTable, GestureActionTable);
} }
// Called when the game starts or when spawned // Called when the game starts or when spawned
+15 -99
View File
@@ -33,107 +33,23 @@ public:
UPROPERTY(BlueprintReadWrite) UPROPERTY(BlueprintReadWrite)
FString GestureName; FString GestureName;
UFUNCTION()
// This checks the gesture and returns an aproximate scor of how well the Gesture was exectuded, needs to be overwritten in child classes.
UFUNCTION(BlueprintCallable)
virtual float Evaluate(AHandActor* Hand) const; virtual float Evaluate(AHandActor* Hand) const;
int32 GetFingerTipIndex(EHandFinger Finger) const protected:
{ // Helper functions to more easily check Gestures
switch (Finger) int32 GetFingerTipIndex(EHandFinger Finger) const;
{ int32 GetFingerBaseIndex(EHandFinger Finger) const;
case EHandFinger::Thumb: return 4; float GetHandScale(const AHandActor* Hand) const;
case EHandFinger::Index: return 8; float GetNormalizedDistance(const AHandActor* Hand, int32 A, int32 B) const;
case EHandFinger::Middle: return 12; float GetPinchScore(const AHandActor* Hand, EHandFinger A, EHandFinger B) const;
case EHandFinger::Ring: return 16; float GetFingerExtendedScore(const AHandActor* Hand, EHandFinger Finger) const;
case EHandFinger::Pinky: return 20; float GetFingerCurledScore(const AHandActor* Hand, EHandFinger Finger) const;
} float GetFingersAverage(const AHandActor* Hand, const TArray<EHandFinger>& Fingers, bool bExtended) const;
return 0; FVector GetFingerDirection(const AHandActor* Hand, EHandFinger Finger) const;
} const FVector& GetPoint(const AHandActor* Hand, int32 Index) const;
int32 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& GetPoint(const AHandActor* Hand, int32 Index) const
{
return Hand->Points[Index];
}
float GetHandScale(const AHandActor* Hand) const
{
const TArray<FVector>& P = Hand->Points;
return FVector::Dist(P[0], P[9]); // wrist -> middle base
}
float GetNormalizedDistance(const AHandActor* Hand, int32 A, int32 B) const
{
float Dist = FVector::Dist(GetPoint(Hand, A), GetPoint(Hand, B));
return Dist / GetHandScale(Hand);
}
float 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 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 GetFingerCurledScore(const AHandActor* Hand, EHandFinger Finger) const
{
return 1.0f - GetFingerExtendedScore(Hand, Finger);
}
float 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 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();
}
}; };
UCLASS() UCLASS()
@@ -49,6 +49,11 @@ public:
// Populated on BeginPlay from JSON // Populated on BeginPlay from JSON
UPROPERTY(BlueprintReadOnly, Category = "Gesture") UPROPERTY(BlueprintReadOnly, Category = "Gesture")
TArray<FGestureBinding> GestureBindings; TArray<FGestureBinding> GestureBindings;
void LoadBindings(const FString& FilePath);
void Initialize(UDataTable* InGestureTable, UDataTable* InActionTable);
void BuildDefinitionMap();
void BuildActionMap();
// Populated on BeginPlay from the tables // Populated on BeginPlay from the tables
UPROPERTY(BlueprintReadOnly, Category = "Gesture") UPROPERTY(BlueprintReadOnly, Category = "Gesture")
@@ -57,8 +62,6 @@ public:
UPROPERTY(BlueprintReadOnly, Category = "Gesture") UPROPERTY(BlueprintReadOnly, Category = "Gesture")
TMap<FString, TSubclassOf<AGestureAction>> ActionClassMap; TMap<FString, TSubclassOf<AGestureAction>> ActionClassMap;
private: private:
// GestureManager.h — add these // GestureManager.h — add these
UPROPERTY(EditDefaultsOnly, Category = "Gesture") UPROPERTY(EditDefaultsOnly, Category = "Gesture")
+7
View File
@@ -71,6 +71,13 @@ public:
const TArray<AHandActor*>& GetHandList() const { return HandList; } const TArray<AHandActor*>& GetHandList() const { return HandList; }
// GestureManager.h — add these
UPROPERTY(EditDefaultsOnly, Category = "Gesture")
UDataTable* GestureDefinitionTable;
UPROPERTY(EditDefaultsOnly, Category = "Gesture")
UDataTable* GestureActionTable;
private: private:
void RemoveDeadHands(); void RemoveDeadHands();
void UpdateHands(); void UpdateHands();