Added GestureAction Ruler, added TableLineActor

This commit is contained in:
DuOtto
2026-07-29 00:07:47 +02:00
parent c9e46eeb41
commit 257f045515
19 changed files with 513 additions and 332 deletions
Binary file not shown.
Binary file not shown.
@@ -197,8 +197,8 @@ bool ADetectionManager::ParseHandPacket(const TArray<uint8>& Bytes, int32& OutSe
FHandPacketHand Hand; FHandPacketHand Hand;
Hand.HandId = (int32)HandId; Hand.HandId = (int32)HandId;
Hand.Handedness = (HandednessByte == 1) ? EHandedness::Left : Hand.Handedness = (HandednessByte == 1) ? EHandedness::Right :
(HandednessByte == 2) ? EHandedness::Right : (HandednessByte == 2) ? EHandedness::Left :
EHandedness::Unknown; EHandedness::Unknown;
Hand.Confidence = Confidence; Hand.Confidence = Confidence;
+3 -1
View File
@@ -18,7 +18,7 @@ void AGestureAction::BeginPlay()
Super::BeginPlay(); Super::BeginPlay();
} }
void AGestureAction::InitAction_Implementation(AHandActor* Hand, const FGestureBinding& NewBinding, float NewUEUnitsPerUnit) void AGestureAction::InitAction_Implementation(AHandActor* Hand, const FGestureBinding& NewBinding, float NewUEUnitsPerUnit, float TableHeight)
{ {
HandA = Hand; HandA = Hand;
bRequiresSecondHand = Binding.LeftGesture != "" && Binding.RightGesture != ""; bRequiresSecondHand = Binding.LeftGesture != "" && Binding.RightGesture != "";
@@ -52,6 +52,8 @@ void AGestureAction::AddOtherHand_Implementation(AHandActor* OtherHand)
void AGestureAction::EndGesture_Implementation() void AGestureAction::EndGesture_Implementation()
{ {
HandA = nullptr;
HandB = nullptr;
DestroyIcons(); DestroyIcons();
Destroy(); Destroy();
} }
@@ -31,6 +31,54 @@ void UGestureManagerComponent::BeginPlay()
LoadBindings("C:/git/Table/Test/Gestures/gesture_config.json"); 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() void UGestureManagerComponent::EvaluateHands()
{ {
TArray<AHandActor*>& Hands = HandManager->HandList; TArray<AHandActor*>& Hands = HandManager->HandList;
@@ -86,7 +134,7 @@ void UGestureManagerComponent::LoadBindings(const FString& FilePath)
const TArray<TSharedPtr<FJsonValue>>* BindingsArray; const TArray<TSharedPtr<FJsonValue>>* BindingsArray;
if (Root->TryGetArrayField(TEXT("bindings"), BindingsArray)) if (Root->TryGetArrayField(TEXT("rules"), BindingsArray))
{ {
for (const TSharedPtr<FJsonValue>& Value : *BindingsArray) for (const TSharedPtr<FJsonValue>& Value : *BindingsArray)
{ {
@@ -126,51 +174,9 @@ void UGestureManagerComponent::LoadBindings(const FString& FilePath)
} }
} }
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<FGestureActionRow*> Rows;
GestureActionTable->GetAllRows(Context, Rows);
for (FGestureActionRow* Row : Rows)
{
if (!Row || !Row->ActionClass) continue;
ActionClassMap.Add( FName(Row->ActionClass->GetName()), Row->ActionClass );
}
}
void UGestureManagerComponent::CheckGestureDefinitions(TArray<AHandActor*>& Hands) void UGestureManagerComponent::CheckGestureDefinitions(TArray<AHandActor*>& Hands)
{ {
@@ -272,7 +278,7 @@ void UGestureManagerComponent::CheckGestureDefinitions(TArray<AHandActor*>& Hand
Hand->CandidateFrames = 1; Hand->CandidateFrames = 1;
Hand->FailFrames = 0; Hand->FailFrames = 0;
break; break;
case EHandGesturePhase::Ongoing: // Checking if the gesture is still ongoing case EHandGesturePhase::Ongoing: // Checking if the gesture is still ongoinga
//if (Hand->ActiveGestureName != BestGesture) Hand->FailFrames++; //if (Hand->ActiveGestureName != BestGesture) Hand->FailFrames++;
//else Hand->FailFrames = 0; //else Hand->FailFrames = 0;
if (Hand->ActiveGestureName != BestGesture) if (Hand->ActiveGestureName != BestGesture)
@@ -313,8 +319,9 @@ void UGestureManagerComponent::CheckGestureDefinitions(TArray<AHandActor*>& Hand
void UGestureManagerComponent::TriggerAction(AHandActor* Hand) void UGestureManagerComponent::TriggerAction(AHandActor* Hand)
{ {
if (Hand->bUsedThisTick) return; if (Hand->bUsedThisTick) return;
if (TryExtendGestureAction(Hand)) return; if (TryExtendGestureAction(Hand)) return;
for (const FGestureBinding& Binding : GestureBindings) for (const FGestureBinding& Binding : GestureBindings)
@@ -323,9 +330,12 @@ void UGestureManagerComponent::TriggerAction(AHandActor* Hand)
|| (Hand->Handedness == EHandedness::Right && Binding.RightGesture == Hand->ActiveGestureName); || (Hand->Handedness == EHandedness::Right && Binding.RightGesture == Hand->ActiveGestureName);
if (!bMatches) continue; if (!bMatches) continue;
AGestureAction* NewAction = SpawnGestureAction(Binding.ActionName, Hand); AGestureAction* NewAction = SpawnGestureAction(Binding.ActionName, Hand);
NewAction->InitAction(Hand, Binding, UEUnitsPerUnit); if (!NewAction) return;
NewAction->InitAction(Hand, Binding, UEUnitsPerUnit, TableHeight);
Hand->Phase = EHandGesturePhase::Ongoing;
return; return;
} }
@@ -334,6 +344,7 @@ void UGestureManagerComponent::TriggerAction(AHandActor* Hand)
void UGestureManagerComponent::UpdateOngoingActions() void UGestureManagerComponent::UpdateOngoingActions()
{ {
for (AGestureAction* Action : ActiveGestureActions) for (AGestureAction* Action : ActiveGestureActions)
{ {
if (!Action) continue; if (!Action) continue;
@@ -344,7 +355,6 @@ void UGestureManagerComponent::UpdateOngoingActions()
AGestureAction* UGestureManagerComponent::SpawnGestureAction(FName ActionName, AHandActor* Hand) AGestureAction* UGestureManagerComponent::SpawnGestureAction(FName ActionName, AHandActor* Hand)
{ {
TSubclassOf<AGestureAction>* ActionClassPtr = ActionClassMap.Find(ActionName); TSubclassOf<AGestureAction>* ActionClassPtr = ActionClassMap.Find(ActionName);
if (!ActionClassPtr || !Hand) return nullptr; if (!ActionClassPtr || !Hand) return nullptr;
@@ -362,6 +372,7 @@ AGestureAction* UGestureManagerComponent::SpawnGestureAction(FName ActionName, A
if (!NewAction) return nullptr; if (!NewAction) return nullptr;
ActiveGestureActions.Add(NewAction);
Hand->ActiveAction = NewAction; Hand->ActiveAction = NewAction;
Hand->bUsedThisTick = true; Hand->bUsedThisTick = true;
@@ -424,7 +435,11 @@ bool UGestureManagerComponent::TryCreateExtension(AHandActor* Hand)
AGestureAction* NewAction = SpawnGestureAction(ActionName, Hand); AGestureAction* NewAction = SpawnGestureAction(ActionName, Hand);
NewAction->InitAction(Hand, OtherHand->ActiveAction->Binding, UEUnitsPerUnit); if (!NewAction) return false;
ActiveGestureActions.Add(NewAction);
NewAction->InitAction(Hand, OtherHand->ActiveAction->Binding, UEUnitsPerUnit, TableHeight);
OtherHand->bUsedThisTick = true; OtherHand->bUsedThisTick = true;
Hand->bUsedThisTick = true; Hand->bUsedThisTick = true;
OtherAction->AddExtension(NewAction); OtherAction->AddExtension(NewAction);
+48 -64
View File
@@ -4,80 +4,64 @@
#include "MyGestureActions.h" #include "MyGestureActions.h"
#include "HandActor.h" #include "HandActor.h"
#include "TableIconActor.h" #include "TableIconActor.h"
#include "MyTableIconActors.h"
void ARulerAction::UpdateAction_Implementation() void AGestureAction_Ruler::InitAction_Implementation(AHandActor* Hand, const FGestureBinding& NewBinding, float NewUEUnitsPerUnit, float TableHeight)
{ {
if (!HandA) return; Super::InitAction_Implementation(Hand, NewBinding, NewUEUnitsPerUnit, TableHeight);
DesiredHeight = -500;
// Update end point to current fingertip StartPoint = HandA->Points[8];
Anchors.Add("End", HandA->Points[8]); StartPoint.Z = DesiredHeight;
FVector Start = Anchors["Start"];
FVector End = Anchors["End"];
float RawDist = FVector::Dist(Start, End);
float Inches = GetSnappedInches(RawDist);
// Move line icon between start and end
if (Icons.IsValidIndex(0) && IsValid(Icons[0]))
{
Icons[0]->SetActorLocation((Start + End) * 0.5f);
// You'll add a SetEndpoints(Start, End) function to ATableIconLineActor later
}
// Move label icon to midpoint, slightly above table
if (Icons.IsValidIndex(1) && IsValid(Icons[1]))
{
FVector Mid = (Start + End) * 0.5f + FVector(0, 0, 5.f);
Icons[1]->SetActorLocation(Mid);
// Icons[1]->SetLabelText(FString::Printf(TEXT("%d\""), (int32)Inches));
}
}
void ARulerAction::AbortAction_Implementation()
{
Super::AbortAction();
}
void ARulerAction::AddExtension_Implementation(AGestureAction* OtherAction)
{
return;
}
void ARulerAction::EndGesture_Implementation()
{
Super::EndGesture();
}
void ARulerAction::InitAction_Implementation(AHandActor* Hand, const FGestureBinding& NewBinding, float NewUEUnitsPerUnit)
{
Super::InitAction(Hand, NewBinding, NewUEUnitsPerUnit);
Anchors.Add("Start", Hand->Points[8]);
FActorSpawnParameters SpawnParams; FActorSpawnParameters SpawnParams;
SpawnParams.Owner = GetOwner(); SpawnParams.Owner = this;
SpawnParams.SpawnCollisionHandlingOverride = SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
AGestureAction* NewAction = nullptr;
/*AGestureAction* NewAction = GetWorld()->SpawnActor<AGestureAction>(
*ActionClassPtr,
FVector::ZeroVector,
FRotator::ZeroRotator,
SpawnParams);
*/
if (!NewAction) return;
if (LineIconClass)
{
LineIcon = GetWorld()->SpawnActor<ATableIconLineActor>(LineIconClass, StartPoint, FRotator::ZeroRotator, SpawnParams);
if (LineIcon)
{
LineIcon->OwningAction = this;
LineIcon->bPersistOnActionEnd = true;
LineIcon->LineThickness = 50;
Icons.Add(LineIcon);
}
}
RulerPoints.Add(FVector::ZeroVector);
RulerPoints.Add(FVector::ZeroVector);
} }
float ARulerAction::GetSnappedInches(float RawDistance) const void AGestureAction_Ruler::UpdateAction_Implementation()
{
if (!HandA || !IsValid(LineIcon)) return;
const FVector Current = HandA->Points[8];
//if (FVector::DistSquared(Current, RulerPoints[RulerPoints.Num() - 1]) < FMath::Square(UEUnitsPerUnit)) return;
RulerPoints[RulerPoints.Num() - 1] = Current - StartPoint;
RulerPoints[RulerPoints.Num() - 1].Z = 0;
LineIcon->SetPoints(RulerPoints);
}
float AGestureAction_Ruler::GetSnappedUnits(float RawDistance) const
{ {
if (UEUnitsPerUnit <= 0.f) return 0.f; if (UEUnitsPerUnit <= 0.f) return 0.f;
float Unit = RawDistance / UEUnitsPerUnit; return FMath::RoundToFloat(RawDistance / UEUnitsPerUnit);
return FMath::RoundToFloat(Unit); // snap to whole inches
} }
float AGestureAction_Ruler::GetTotalLength() const
{
float Total = 0.f;
for (int32 i = 0; i < RulerPoints.Num() - 1; i++)
Total += FVector::Dist(RulerPoints[i], RulerPoints[i + 1]);
return Total;
}
void AGestureAction_Ruler::AddRulerPoint()
{
FVector Current = HandA->Points[8];
RulerPoints.Add(Current - StartPoint);
}
@@ -10,118 +10,104 @@
ATableIconLineActor::ATableIconLineActor() ATableIconLineActor::ATableIconLineActor()
{ {
PrimaryActorTick.bCanEverTick = false; // purely driven by SetPoints
USceneComponent* Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
SetRootComponent(Root);
MeshComponent = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("LineMesh")); MeshComponent = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("LineMesh"));
MeshComponent->SetupAttachment(RootComponent); MeshComponent->SetupAttachment(RootComponent);
MeshComponent->SetCastShadow(false); MeshComponent->SetCastShadow(false);
MeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
} }
void ATableIconLineActor::SetEndpoints(const FVector& Start, const FVector& End) void ATableIconLineActor::SetPoints(const TArray<FVector>& NewPoints)
{ {
// Skip rebuild if nothing meaningful changed const FTransform& ActorTransform = GetActorTransform();
if (Start.Equals(LastStart, 0.1f) && End.Equals(LastEnd, 0.1f)) return;
LastStart = Start;
LastEnd = End;
BuildLineMesh(Start, End); Points.Reset(NewPoints.Num());
if (bShowTickMarks) BuildTickMesh(Start, End);
Points = NewPoints;
MeshComponent->ClearMeshSection(0);
BuildLineMesh();
if (LineMaterial) MeshComponent->SetMaterial(0, LineMaterial);
} }
void ATableIconLineActor::BuildLineMesh(const FVector& Start, const FVector& End) void ATableIconLineActor::BuildLineMesh()
{ {
FVector Dir = (End - Start).GetSafeNormal(); TArray<FVector> Vertices;
FVector Up = FVector::UpVector; TArray<int32> Triangles;
FVector Right = FVector::CrossProduct(Dir, Up).GetSafeNormal() * LineThickness * 0.5f; TArray<FVector> Normals;
TArray<FVector2D> UVs;
TArray<FColor> Colors;
TArray<FProcMeshTangent> Tangents;
// A flat quad along the line, facing up FVector Direction = FVector::ZeroVector;
TArray<FVector> Vertices; const float HalfLineThickness = LineThickness * 0.5f;
TArray<int32> Triangles;
TArray<FVector> Normals; for (int32 i = 0; i < Points.Num() - 1; i++) {
TArray<FVector2D> UVs; const FVector& PointA = Points[i];
TArray<FColor> Colors; const FVector& PointB = Points[i + 1];
TArray<FProcMeshTangent> Tangents;
Vertices.Add(Start - Right); // 0 Direction = (PointB - PointA).GetSafeNormal();
Vertices.Add(Start + Right); // 1
Vertices.Add(End + Right); // 2
Vertices.Add(End - Right); // 3
Triangles = { 0, 1, 2, 0, 2, 3 }; const FVector Right = FVector::CrossProduct(FVector::UpVector, Direction).GetSafeNormal();
for (int i = 0; i < 4; i++) const FVector Offset = Right * (LineThickness * 0.5f);
{
Normals.Add(FVector::UpVector);
UVs.Add(FVector2D(i < 2 ? 0.f : 1.f, i % 2 == 0 ? 0.f : 1.f));
Colors.Add(LineColor.ToFColor(true));
Tangents.Add(FProcMeshTangent(Dir, false));
}
// Section 0 = main line
MeshComponent->CreateMeshSection(0, Vertices, Triangles, Normals, UVs,
Colors, Tangents, false);
}
void ATableIconLineActor::BuildTickMesh(const FVector& Start, const FVector& End)
{
if (UEUnitsPerUnit <= 0.f || TickInterval <= 0.f) return;
float TotalDist = FVector::Dist(Start, End);
float TickSpacing = TickInterval * UEUnitsPerUnit;
int32 NumTicks = FMath::FloorToInt(TotalDist / TickSpacing);
if (NumTicks <= 0)
{
MeshComponent->ClearMeshSection(1);
return;
}
FVector Dir = (End - Start).GetSafeNormal();
FVector Right = FVector::CrossProduct(Dir, FVector::UpVector).GetSafeNormal();
TArray<FVector> Vertices;
TArray<int32> Triangles;
TArray<FVector> Normals;
TArray<FVector2D> UVs;
TArray<FColor> Colors;
TArray<FProcMeshTangent> Tangents;
float TickHalfLen = LineThickness * 3.f;
float TickHalfW = LineThickness * 0.5f;
for (int32 i = 1; i <= NumTicks; i++)
{
FVector Center = Start + Dir * (TickSpacing * i);
FVector A = Center - Right * TickHalfLen - Dir * TickHalfW;
FVector B = Center + Right * TickHalfLen - Dir * TickHalfW;
FVector C = Center + Right * TickHalfLen + Dir * TickHalfW;
FVector D = Center - Right * TickHalfLen + Dir * TickHalfW;
int32 Base = Vertices.Num(); int32 Base = Vertices.Num();
Vertices.Add(A); Vertices.Add(B);
Vertices.Add(C); Vertices.Add(D);
Triangles.Add(Base); Triangles.Add(Base + 1); Triangles.Add(Base + 2); Vertices.Add(PointA - Offset);
Triangles.Add(Base); Triangles.Add(Base + 2); Triangles.Add(Base + 3); Vertices.Add(PointA + Offset);
Vertices.Add(PointB + Offset);
Vertices.Add(PointB - Offset);
for (int j = 0; j < 4; j++) Triangles.Add(Base + 0);
{ Triangles.Add(Base + 1);
Normals.Add(FVector::UpVector); Triangles.Add(Base + 2);
UVs.Add(FVector2D::ZeroVector);
Colors.Add(LineColor.ToFColor(true)); Triangles.Add(Base + 0);
Tangents.Add(FProcMeshTangent(Dir, false)); Triangles.Add(Base + 2);
} Triangles.Add(Base + 3);
} }
// Section 1 = tick marks
MeshComponent->CreateMeshSection(1, Vertices, Triangles, Normals, UVs, for (int i = 0; i < Vertices.Num(); i++)
Colors, Tangents, false); {
Normals.Add(FVector::UpVector);
}
for (int i = 0; i < Vertices.Num(); i++)
{
UVs.Add(FVector2D::ZeroVector);
}
for (int i = 0; i < Vertices.Num(); i++)
{
Colors.Add(FColor::Red);
}
for (int i = 0; i < Vertices.Num(); i++)
{
Tangents.Add(FProcMeshTangent(Direction, false));
}
MeshComponent->CreateMeshSection(0, Vertices, Triangles, Normals, UVs, Colors, Tangents, false);
} }
ATableIconWidgetActor::ATableIconWidgetActor() ATableIconWidgetActor::ATableIconWidgetActor()
{ {
USceneComponent* Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
SetRootComponent(Root);
PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.bCanEverTick = true;
WidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("WidgetComponent")); WidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("WidgetComponent"));
@@ -140,15 +126,13 @@ void ATableIconWidgetActor::Tick(float DeltaTime)
{ {
Super::Tick(DeltaTime); Super::Tick(DeltaTime);
if (!bFaceCamera) return;
APlayerCameraManager* CamManager = UGameplayStatics::GetPlayerCameraManager(this, 0); APlayerCameraManager* CamManager = UGameplayStatics::GetPlayerCameraManager(this, 0);
if (!CamManager) return; if (!CamManager) return;
FVector CamLocation = CamManager->GetCameraLocation(); FVector CamLocation = CamManager->GetCameraLocation();
FVector ToCamera = (CamLocation - GetActorLocation()).GetSafeNormal(); FVector ToCamera = (CamLocation - GetActorLocation()).GetSafeNormal();
// Only rotate on yaw keeps widget flat relative to table // Only rotate on yaw keeps widget flat relative to table
ToCamera.Z = 0.f; ToCamera.Z = 0.f;
if (!ToCamera.IsNearlyZero()) if (!ToCamera.IsNearlyZero())
SetActorRotation(ToCamera.ToOrientationRotator()); SetActorRotation(ToCamera.ToOrientationRotator());
@@ -164,4 +148,12 @@ void ATableIconWidgetActor::SetWidgetClass(TSubclassOf<UUserWidget> InWidgetClas
UUserWidget* ATableIconWidgetActor::GetWidget() const UUserWidget* ATableIconWidgetActor::GetWidget() const
{ {
return WidgetComponent->GetUserWidgetObject(); return WidgetComponent->GetUserWidgetObject();
}
void ATableIconLabelActor::SetDistance(float RealWorldUnits, const FString& UnitSuffix)
{
int32 Snapped = FMath::RoundToInt(RealWorldUnits);
SetLabelText(FString::Printf(TEXT("%d%s"), Snapped, *UnitSuffix));
} }
+15 -46
View File
@@ -26,53 +26,22 @@ protected:
public: public:
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite) UPROPERTY(EditDefaultsOnly, BlueprintReadWrite) float UEUnitsPerUnit = 10.f;
float UEUnitsPerUnit = 100.f; UPROPERTY(EditDefaultsOnly) bool bRequiresSecondHand = false;
UPROPERTY(EditDefaultsOnly) bool bExtendable = false;
UPROPERTY(EditDefaultsOnly) FGestureBinding Binding;
UPROPERTY(BlueprintReadWrite) TArray<ATableIconActor*> Icons;
UPROPERTY(BlueprintReadWrite) AHandActor* HandA;
UPROPERTY(BlueprintReadWrite) AHandActor* HandB;
UPROPERTY(BlueprintReadWrite) AGestureAction* LinkedActions;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stuff") AHandManager* HandManager;
UPROPERTY(EditDefaultsOnly) UFUNCTION(BlueprintCallable, BlueprintNativeEvent) void InitAction(AHandActor* Hand, const FGestureBinding& NewBinding, float NewUEUnitsPerUnit, float TableHeight);
bool bRequiresSecondHand = false; UFUNCTION(BlueprintCallable, BlueprintNativeEvent) void UpdateAction();
UFUNCTION(BlueprintCallable, BlueprintNativeEvent) void AbortAction();
UPROPERTY(EditDefaultsOnly) UFUNCTION(BlueprintCallable, BlueprintNativeEvent) void AddExtension(AGestureAction* OtherAction);
bool bExtendable = false; UFUNCTION(BlueprintCallable, BlueprintNativeEvent) void AddOtherHand(AHandActor* OtherHand);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent) void EndGesture();
UPROPERTY(EditDefaultsOnly)
FGestureBinding Binding;
UPROPERTY(BlueprintReadWrite)
TArray<ATableIconActor*> Icons;
UPROPERTY(BlueprintReadWrite)
TMap<FName, FVector> Anchors;
UPROPERTY(BlueprintReadWrite)
AHandActor* HandA;
UPROPERTY(BlueprintReadWrite)
AHandActor* HandB;
UPROPERTY(BlueprintReadWrite)
AGestureAction* LinkedActions;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stuff")
AHandManager* HandManager;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void InitAction(AHandActor* Hand, const FGestureBinding& NewBinding, float NewUEUnitsPerUnit);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void UpdateAction();
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void AbortAction();
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void AddExtension(AGestureAction* OtherAction);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void AddOtherHand(AHandActor* OtherHand);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void EndGesture();
void DestroyIcons(); void DestroyIcons();
}; };
@@ -24,34 +24,25 @@ protected:
public: public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Scale") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Scale") float UEUnitsPerUnit = 10.f; // set via calibration rectangle
float UEUnitsPerUnit = 100.f; // set via calibration rectangle UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Scale") float TableHeight = -650.f;
UPROPERTY(BlueprintReadOnly) AHandManager* HandManager;
UPROPERTY(BlueprintReadOnly) UFUNCTION(BlueprintCallable, Category = "Gesture") void EvaluateHands();
AHandManager* HandManager;
UFUNCTION(BlueprintCallable, Category = "Gesture")
void EvaluateHands();
UFUNCTION() UFUNCTION() void OnHandAdded(AHandActor* Hand);
void OnHandAdded(AHandActor* Hand);
UFUNCTION() UFUNCTION() void OnHandRemoved(AHandActor* Hand);
void OnHandRemoved(AHandActor* Hand);
UPROPERTY(EditDefaultsOnly, Category = "Gesture") UPROPERTY(EditDefaultsOnly, Category = "Gesture") FString BindingConfigPath; // path to the per-game JSON
FString BindingConfigPath; // path to the per-game JSON
// Loaded from JSON — gesture name -> definition object // Loaded from JSON — gesture name -> definition object
UPROPERTY(BlueprintReadOnly, Category = "Gesture") UPROPERTY(BlueprintReadOnly, Category = "Gesture") TArray<UGestureDefinition*> GestureDefinitions;
TArray<UGestureDefinition*> GestureDefinitions;
UPROPERTY(BlueprintReadOnly, Category = "Gesture") UPROPERTY(BlueprintReadOnly, Category = "Gesture") TArray<AGestureAction*> ActiveGestureActions;
TArray<AGestureAction*> ActiveGestureActions;
// 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 LoadBindings(const FString& FilePath);
void Initialize(UDataTable* InGestureTable, UDataTable* InActionTable); void Initialize(UDataTable* InGestureTable, UDataTable* InActionTable);
@@ -59,19 +50,15 @@ public:
void BuildActionMap(); void BuildActionMap();
// Populated on BeginPlay from the tables // Populated on BeginPlay from the tables
UPROPERTY(BlueprintReadOnly, Category = "Gesture") UPROPERTY(BlueprintReadOnly, Category = "Gesture") TMap<FName, UGestureDefinition*> DefinitionMap;
TMap<FName, UGestureDefinition*> DefinitionMap;
UPROPERTY(BlueprintReadOnly, Category = "Gesture") UPROPERTY(BlueprintReadOnly, Category = "Gesture") TMap<FName, TSubclassOf<AGestureAction>> ActionClassMap;
TMap<FName, TSubclassOf<AGestureAction>> ActionClassMap;
private: private:
// //
UPROPERTY(EditDefaultsOnly, Category = "Gesture") UPROPERTY(EditDefaultsOnly, Category = "Gesture") UDataTable* GestureDefinitionTable;
UDataTable* GestureDefinitionTable;
UPROPERTY(EditDefaultsOnly, Category = "Gesture") UPROPERTY(EditDefaultsOnly, Category = "Gesture") UDataTable* GestureActionTable;
UDataTable* GestureActionTable;
void CheckGestureDefinitions(TArray<AHandActor*>& Hands); void CheckGestureDefinitions(TArray<AHandActor*>& Hands);
+28 -10
View File
@@ -6,6 +6,8 @@
#include "GestureAction.h" #include "GestureAction.h"
#include "MyGestureActions.generated.h" #include "MyGestureActions.generated.h"
class ATableIconLineActor;
class ATableIconLabelActor;
/** /**
* *
*/ */
@@ -14,23 +16,39 @@ class TABLE_API AMyGestureActions : public AGestureAction
{ {
GENERATED_BODY() GENERATED_BODY()
//virtual void InitAction_Implementation(AHandActor* Hand, const FGestureBinding& NewBinding, float NewUEUnitsPerUnit) override;
//virtual void UpdateAction_Implementation() override;
//virtual void EndGesture_Implementation() override;
//virtual void AddOtherHand_Implementation(AHandActor* OtherHand) override;
}; };
UCLASS() UCLASS(Blueprintable)
class TABLE_API ARulerAction : public AGestureAction class TABLE_API AGestureAction_Ruler : public AGestureAction
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
void UpdateAction_Implementation() override; // Set these in BP defaults — point at your BP subclasses
void AbortAction_Implementation() override; UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Ruler") TSubclassOf<ATableIconLineActor> LineIconClass;
void AddExtension_Implementation(AGestureAction* OtherAction) override; UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Ruler") TSubclassOf<ATableIconLabelActor> LabelIconClass;
void EndGesture_Implementation() override; UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Ruler") FString UnitSuffix = TEXT("\"");
void InitAction_Implementation(AHandActor* Hand, const FGestureBinding& NewBinding, float NewUEUnitsPerUnit) override; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ruler") TArray<FVector> RulerPoints;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Line") float DesiredHeight = -650.f;
UFUNCTION(BlueprintCallable, BlueprintPure) float GetTotalLength() const;
UFUNCTION(BlueprintCallable) void AddRulerPoint();
virtual void InitAction_Implementation(AHandActor* Hand, const FGestureBinding& NewBinding, float NewUEUnitsPerUnit, float TableHeight) override;
virtual void UpdateAction_Implementation() override;
private: private:
float GetSnappedInches(float RawDistance) const; UPROPERTY() ATableIconLineActor* LineIcon = nullptr;
UPROPERTY() ATableIconLabelActor* LabelIcon = nullptr;
UPROPERTY() FVector StartPoint;
float GetSnappedUnits(float RawDistance) const;
}; };
+24 -40
View File
@@ -30,34 +30,20 @@ class TABLE_API ATableIconLineActor : public ATableIconActor
public: public:
ATableIconLineActor(); ATableIconLineActor();
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable) void SetPoints(const TArray<FVector>& InPoints);
void SetEndpoints(const FVector& Start, const FVector& End);
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
float LineThickness = 1.f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Line") float LineThickness = 10.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Line") FLinearColor LineColor = FLinearColor::Blue;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Line") UMaterialInterface* LineMaterial = nullptr;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite) protected:
FLinearColor LineColor = FLinearColor::White; UPROPERTY(BlueprintReadOnly, Category = "Line") TArray<FVector> Points;
// Tick markers along the line — e.g. every inch
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
bool bShowTickMarks = true;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
float TickInterval = 1.f; // in real-world units
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
float UEUnitsPerUnit = 100.f;
private: private:
UPROPERTY() UPROPERTY() UProceduralMeshComponent* MeshComponent;
UProceduralMeshComponent* MeshComponent;
void BuildLineMesh(const FVector& Start, const FVector& End); void BuildLineMesh();
void BuildTickMesh(const FVector& Start, const FVector& End);
FVector LastStart;
FVector LastEnd;
}; };
@@ -70,24 +56,22 @@ class TABLE_API ATableIconWidgetActor : public ATableIconActor
public: public:
ATableIconWidgetActor(); ATableIconWidgetActor();
UPROPERTY(VisibleAnywhere, BlueprintReadOnly) UPROPERTY(VisibleAnywhere, BlueprintReadOnly) UWidgetComponent* WidgetComponent;
UWidgetComponent* WidgetComponent; UPROPERTY(VisibleAnywhere, BlueprintReadOnly) UWidgetInteractionComponent* WidgetInteraction;
UFUNCTION(BlueprintCallable) void SetWidgetClass(TSubclassOf<UUserWidget> InWidgetClass);
UPROPERTY(VisibleAnywhere, BlueprintReadOnly) UFUNCTION(BlueprintCallable, BlueprintPure) UUserWidget* GetWidget() const;
UWidgetInteractionComponent* WidgetInteraction;
// Face toward a target each tick — useful for always facing the camera
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
bool bFaceCamera = true;
// Set widget content — callable from GestureAction
UFUNCTION(BlueprintCallable)
void SetWidgetClass(TSubclassOf<UUserWidget> InWidgetClass);
// Get the underlying widget cast to a specific type in BP
UFUNCTION(BlueprintCallable, BlueprintPure)
UUserWidget* GetWidget() const;
protected: protected:
virtual void Tick(float DeltaTime) override; virtual void Tick(float DeltaTime) override;
};
UCLASS(Blueprintable)
class TABLE_API ATableIconLabelActor : public ATableIconWidgetActor
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintImplementableEvent, BlueprintCallable) void SetLabelText(const FString& Text);
UFUNCTION(BlueprintCallable) void SetDistance(float RealWorldUnits, const FString& UnitSuffix = TEXT("\""));
}; };
+6 -6
View File
@@ -6,6 +6,8 @@
#include "GameFramework/Actor.h" #include "GameFramework/Actor.h"
#include "TableIconActor.generated.h" #include "TableIconActor.generated.h"
class AGestureAction;
UCLASS() UCLASS()
class TABLE_API ATableIconActor : public AActor class TABLE_API ATableIconActor : public AActor
{ {
@@ -21,16 +23,14 @@ protected:
public: public:
// If true, GestureAction will not destroy this on EndGesture // If true, GestureAction will not destroy this on EndGesture
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite) UPROPERTY(EditDefaultsOnly, BlueprintReadWrite) bool bPersistOnActionEnd = false;
bool bPersistOnActionEnd = false; UPROPERTY(EditAnywhere) AGestureAction* OwningAction;
// Called by GestureAction on end — BP overrides this for fade/lock behavior // Called by GestureAction on end — BP overrides this for fade/lock behavior
UFUNCTION(BlueprintNativeEvent) UFUNCTION(BlueprintNativeEvent) void OnActionEnded();
void OnActionEnded();
// Called by GestureAction every tick // Called by GestureAction every tick
UFUNCTION(BlueprintNativeEvent) UFUNCTION(BlueprintNativeEvent) void OnActionUpdated();
void OnActionUpdated();
}; };
@@ -0,0 +1,69 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyGestureActions.h"
#include "HandActor.h"
#include "TableIconActor.h"
#include "MyTableIconActors.h"
void AGestureAction_Ruler::InitAction_Implementation(AHandActor* Hand, const FGestureBinding& NewBinding, float NewUEUnitsPerUnit, float TableHeight)
{
Super::InitAction_Implementation(Hand, NewBinding, NewUEUnitsPerUnit, TableHeight);
DesiredHeight = -500;
StartPoint = HandA->Points[8];
StartPoint.Z = DesiredHeight;
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = this;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
if (LineIconClass)
{
LineIcon = GetWorld()->SpawnActor<ATableIconLineActor>(LineIconClass, StartPoint, FRotator::ZeroRotator, SpawnParams);
if (LineIcon)
{
LineIcon->OwningAction = this;
LineIcon->bPersistOnActionEnd = true;
LineIcon->LineThickness = 50;
Icons.Add(LineIcon);
}
}
RulerPoints.Add(FVector(0, 0, 0));
RulerPoints.Add(FVector(0, 0, DesiredHeight));
}
void AGestureAction_Ruler::UpdateAction_Implementation()
{
if (!HandA || !IsValid(LineIcon)) return;
const FVector Current = HandA->Points[8];
//if (FVector::DistSquared(Current, RulerPoints[RulerPoints.Num() - 1]) < FMath::Square(UEUnitsPerUnit)) return;
//RulerPoints[RulerPoints.Num() - 1] = Current;
RulerPoints[RulerPoints.Num() - 1] = Current - StartPoint;
RulerPoints[RulerPoints.Num() - 1].Z = 0;
LineIcon->SetPoints(RulerPoints);
}
float AGestureAction_Ruler::GetSnappedUnits(float RawDistance) const
{
if (UEUnitsPerUnit <= 0.f) return 0.f;
return FMath::RoundToFloat(RawDistance / UEUnitsPerUnit);
}
float AGestureAction_Ruler::GetTotalLength() const
{
float Total = 0.f;
for (int32 i = 0; i < RulerPoints.Num() - 1; i++)
Total += FVector::Dist(RulerPoints[i], RulerPoints[i + 1]);
return Total;
}
void AGestureAction_Ruler::AddRulerPoint()
{
FVector Current = HandA->Points[8];
//Current.Z = DesiredHeight;
RulerPoints.Add(Current - StartPoint);
}
@@ -0,0 +1,161 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyTableIconActors.h"
#include "KismetProceduralMeshLibrary.h"
#include "TableIconActor.h"
#include "Kismet/GameplayStatics.h"
#include "Camera/CameraActor.h"
ATableIconLineActor::ATableIconLineActor()
{
PrimaryActorTick.bCanEverTick = false; // purely driven by SetPoints
USceneComponent* Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
SetRootComponent(Root);
MeshComponent = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("LineMesh"));
MeshComponent->SetupAttachment(RootComponent);
MeshComponent->SetCastShadow(false);
MeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
void ATableIconLineActor::SetPoints(const TArray<FVector>& NewPoints)
{
const FTransform& ActorTransform = GetActorTransform();
Points.Reset(NewPoints.Num());
Points = NewPoints;
MeshComponent->ClearMeshSection(0);
BuildLineMesh();
if (LineMaterial) MeshComponent->SetMaterial(0, LineMaterial);
}
void ATableIconLineActor::BuildLineMesh()
{
TArray<FVector> Vertices;
TArray<int32> Triangles;
TArray<FVector> Normals;
TArray<FVector2D> UVs;
TArray<FColor> Colors;
TArray<FProcMeshTangent> Tangents;
FVector Direction = FVector::ZeroVector;
const float HalfLineThickness = LineThickness * 0.5f;
for (int32 i = 0; i < Points.Num() - 1; i++) {
const FVector& PointA = Points[i];
const FVector& PointB = Points[i + 1];
Direction = (PointB - PointA).GetSafeNormal();
const FVector Right = FVector::CrossProduct(FVector::UpVector, Direction).GetSafeNormal();
const FVector Offset = Right * (LineThickness * 0.5f);
int32 Base = Vertices.Num();
Vertices.Add(PointA - Offset);
Vertices.Add(PointA + Offset);
Vertices.Add(PointB + Offset);
Vertices.Add(PointB - Offset);
Triangles.Add(Base + 0);
Triangles.Add(Base + 1);
Triangles.Add(Base + 2);
Triangles.Add(Base + 0);
Triangles.Add(Base + 2);
Triangles.Add(Base + 3);
}
for (int i = 0; i < Vertices.Num(); i++)
{
Normals.Add(FVector::UpVector);
}
for (int i = 0; i < Vertices.Num(); i++)
{
UVs.Add(FVector2D::ZeroVector);
}
for (int i = 0; i < Vertices.Num(); i++)
{
Colors.Add(FColor::Red);
}
for (int i = 0; i < Vertices.Num(); i++)
{
Tangents.Add(FProcMeshTangent(Direction, false));
}
MeshComponent->CreateMeshSection(0, Vertices, Triangles, Normals, UVs, Colors, Tangents, false);
}
ATableIconWidgetActor::ATableIconWidgetActor()
{
USceneComponent* Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
SetRootComponent(Root);
PrimaryActorTick.bCanEverTick = true;
WidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("WidgetComponent"));
WidgetComponent->SetupAttachment(RootComponent);
WidgetComponent->SetDrawAtDesiredSize(true);
WidgetComponent->SetWidgetSpace(EWidgetSpace::World);
WidgetComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
WidgetInteraction = CreateDefaultSubobject<UWidgetInteractionComponent>(TEXT("WidgetInteraction"));
WidgetInteraction->SetupAttachment(RootComponent);
WidgetInteraction->InteractionSource = EWidgetInteractionSource::Custom;
WidgetInteraction->InteractionDistance = 10.f;
}
void ATableIconWidgetActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
APlayerCameraManager* CamManager = UGameplayStatics::GetPlayerCameraManager(this, 0);
if (!CamManager) return;
FVector CamLocation = CamManager->GetCameraLocation();
FVector ToCamera = (CamLocation - GetActorLocation()).GetSafeNormal();
// Only rotate on yaw keeps widget flat relative to table
ToCamera.Z = 0.f;
if (!ToCamera.IsNearlyZero())
SetActorRotation(ToCamera.ToOrientationRotator());
}
void ATableIconWidgetActor::SetWidgetClass(TSubclassOf<UUserWidget> InWidgetClass)
{
if (!InWidgetClass) return;
WidgetComponent->SetWidgetClass(InWidgetClass);
WidgetComponent->InitWidget();
}
UUserWidget* ATableIconWidgetActor::GetWidget() const
{
return WidgetComponent->GetUserWidgetObject();
}
void ATableIconLabelActor::SetDistance(float RealWorldUnits, const FString& UnitSuffix)
{
int32 Snapped = FMath::RoundToInt(RealWorldUnits);
SetLabelText(FString::Printf(TEXT("%d%s"), Snapped, *UnitSuffix));
}