Adding Json Mapping for Gestures
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user