Compare commits

..

3 Commits

Author SHA1 Message Date
DuOtto c9a5c2c6ca Added UI for adjusting TableHight and Scale 2026-07-30 19:31:20 +02:00
DuOtto 85ab894665 Added LineIcon 2026-07-29 14:50:46 +02:00
DuOtto 257f045515 Added GestureAction Ruler, added TableLineActor 2026-07-29 00:07:47 +02:00
28 changed files with 322 additions and 395 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -197,8 +197,8 @@ bool ADetectionManager::ParseHandPacket(const TArray<uint8>& Bytes, int32& OutSe
FHandPacketHand Hand;
Hand.HandId = (int32)HandId;
Hand.Handedness = (HandednessByte == 1) ? EHandedness::Left :
(HandednessByte == 2) ? EHandedness::Right :
Hand.Handedness = (HandednessByte == 1) ? EHandedness::Right :
(HandednessByte == 2) ? EHandedness::Left :
EHandedness::Unknown;
Hand.Confidence = Confidence;
+3 -1
View File
@@ -18,7 +18,7 @@ void AGestureAction::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;
bRequiresSecondHand = Binding.LeftGesture != "" && Binding.RightGesture != "";
@@ -52,6 +52,8 @@ void AGestureAction::AddOtherHand_Implementation(AHandActor* OtherHand)
void AGestureAction::EndGesture_Implementation()
{
HandA = nullptr;
HandB = nullptr;
DestroyIcons();
Destroy();
}
@@ -31,6 +31,54 @@ void UGestureManagerComponent::BeginPlay()
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;
@@ -86,7 +134,7 @@ void UGestureManagerComponent::LoadBindings(const FString& FilePath)
const TArray<TSharedPtr<FJsonValue>>* BindingsArray;
if (Root->TryGetArrayField(TEXT("bindings"), BindingsArray))
if (Root->TryGetArrayField(TEXT("rules"), 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)
{
@@ -272,7 +278,7 @@ void UGestureManagerComponent::CheckGestureDefinitions(TArray<AHandActor*>& Hand
Hand->CandidateFrames = 1;
Hand->FailFrames = 0;
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++;
//else Hand->FailFrames = 0;
if (Hand->ActiveGestureName != BestGesture)
@@ -313,8 +319,9 @@ void UGestureManagerComponent::CheckGestureDefinitions(TArray<AHandActor*>& Hand
void UGestureManagerComponent::TriggerAction(AHandActor* Hand)
{
if (Hand->bUsedThisTick) return;
if (TryExtendGestureAction(Hand)) return;
for (const FGestureBinding& Binding : GestureBindings)
@@ -323,9 +330,12 @@ void UGestureManagerComponent::TriggerAction(AHandActor* Hand)
|| (Hand->Handedness == EHandedness::Right && Binding.RightGesture == Hand->ActiveGestureName);
if (!bMatches) continue;
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;
}
@@ -334,6 +344,7 @@ void UGestureManagerComponent::TriggerAction(AHandActor* Hand)
void UGestureManagerComponent::UpdateOngoingActions()
{
for (AGestureAction* Action : ActiveGestureActions)
{
if (!Action) continue;
@@ -344,7 +355,6 @@ void UGestureManagerComponent::UpdateOngoingActions()
AGestureAction* UGestureManagerComponent::SpawnGestureAction(FName ActionName, AHandActor* Hand)
{
TSubclassOf<AGestureAction>* ActionClassPtr = ActionClassMap.Find(ActionName);
if (!ActionClassPtr || !Hand) return nullptr;
@@ -362,6 +372,7 @@ AGestureAction* UGestureManagerComponent::SpawnGestureAction(FName ActionName, A
if (!NewAction) return nullptr;
ActiveGestureActions.Add(NewAction);
Hand->ActiveAction = NewAction;
Hand->bUsedThisTick = true;
@@ -424,7 +435,11 @@ bool UGestureManagerComponent::TryCreateExtension(AHandActor* 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;
Hand->bUsedThisTick = true;
OtherAction->AddExtension(NewAction);
+66 -64
View File
@@ -4,80 +4,82 @@
#include "MyGestureActions.h"
#include "HandActor.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;
// Update end point to current fingertip
Anchors.Add("End", HandA->Points[8]);
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]);
Super::InitAction_Implementation(Hand, NewBinding, NewUEUnitsPerUnit, TableHeight);
DesiredHeight = -500;
StartPoint = HandA->Points[8];
StartPoint.Z = DesiredHeight;
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = GetOwner();
SpawnParams.SpawnCollisionHandlingOverride =
ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
AGestureAction* NewAction = nullptr;
/*AGestureAction* NewAction = GetWorld()->SpawnActor<AGestureAction>(
*ActionClassPtr,
FVector::ZeroVector,
FRotator::ZeroRotator,
SpawnParams);
*/
if (!NewAction) return;
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::ZeroVector);
RulerPoints.Add(FVector::ZeroVector);
if (LabelIconClass)
{
LabelIcon = GetWorld()->SpawnActor<ATableIconLabelActor>(LabelIconClass, StartPoint, FRotator::ZeroRotator, SpawnParams);
if (LabelIcon)
{
LabelIcon->OwningAction = this;
LabelIcon->bPersistOnActionEnd = true;
LabelIcon->UpdateLabel(StartPoint, 0.f);
Icons.Add(LabelIcon);
}
}
}
float ARulerAction::GetSnappedInches(float RawDistance) const
void AGestureAction_Ruler::UpdateAction_Implementation()
{
if (!HandA || !IsValid(LineIcon) || !IsValid(LabelIcon)) return;
const FVector Current = HandA->Points[8];
//if (FVector::DistSquared(Current, RulerPoints[RulerPoints.Num() - 1]) < FMath::Square(UEUnitsPerUnit)) return;
RulerPoints.Last() = Current - StartPoint;
RulerPoints.Last().Z = 0;
LineIcon->SetPoints(RulerPoints);
// RulerPoints are local to the line actor, so convert back to world
FVector LabelPosition = StartPoint + (RulerPoints.Last() * 0.5f);
LabelPosition.Z = 10;
LabelIcon->UpdateLabel(LabelPosition, GetSnappedUnits(GetTotalLength()));
}
float AGestureAction_Ruler::GetSnappedUnits(float RawDistance) const
{
if (UEUnitsPerUnit <= 0.f) return 0.f;
float Unit = RawDistance / UEUnitsPerUnit;
return FMath::RoundToFloat(Unit); // snap to whole inches
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];
RulerPoints.Add(Current - StartPoint);
}
+81 -106
View File
@@ -10,119 +10,102 @@
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::SetEndpoints(const FVector& Start, const FVector& End)
void ATableIconLineActor::SetPoints(const TArray<FVector>& NewPoints)
{
// Skip rebuild if nothing meaningful changed
if (Start.Equals(LastStart, 0.1f) && End.Equals(LastEnd, 0.1f)) return;
LastStart = Start;
LastEnd = End;
const FTransform& ActorTransform = GetActorTransform();
BuildLineMesh(Start, End);
if (bShowTickMarks) BuildTickMesh(Start, End);
Points.Reset(NewPoints.Num());
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();
FVector Up = FVector::UpVector;
FVector Right = FVector::CrossProduct(Dir, Up).GetSafeNormal() * LineThickness * 0.5f;
TArray<FVector> Vertices;
TArray<int32> Triangles;
TArray<FVector> Normals;
TArray<FVector2D> UVs;
TArray<FColor> Colors;
TArray<FProcMeshTangent> Tangents;
// A flat quad along the line, facing up
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];
Vertices.Add(Start - Right); // 0
Vertices.Add(Start + Right); // 1
Vertices.Add(End + Right); // 2
Vertices.Add(End - Right); // 3
Direction = (PointB - PointA).GetSafeNormal();
Triangles = { 0, 1, 2, 0, 2, 3 };
const FVector Right = FVector::CrossProduct(FVector::UpVector, Direction).GetSafeNormal();
for (int i = 0; i < 4; i++)
{
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;
const FVector Offset = Right * (LineThickness * 0.5f);
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);
Triangles.Add(Base); Triangles.Add(Base + 2); Triangles.Add(Base + 3);
Vertices.Add(PointA - Offset);
Vertices.Add(PointA + Offset);
Vertices.Add(PointB + Offset);
Vertices.Add(PointB - Offset);
for (int j = 0; j < 4; j++)
{
Normals.Add(FVector::UpVector);
UVs.Add(FVector2D::ZeroVector);
Colors.Add(LineColor.ToFColor(true));
Tangents.Add(FProcMeshTangent(Dir, false));
}
Triangles.Add(Base + 0);
Triangles.Add(Base + 1);
Triangles.Add(Base + 2);
Triangles.Add(Base + 0);
Triangles.Add(Base + 2);
Triangles.Add(Base + 3);
}
// Section 1 = tick marks
MeshComponent->CreateMeshSection(1, Vertices, Triangles, Normals, UVs,
Colors, Tangents, false);
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()
{
PrimaryActorTick.bCanEverTick = true;
USceneComponent* Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
SetRootComponent(Root);
PrimaryActorTick.bCanEverTick = false;
WidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("WidgetComponent"));
WidgetComponent->SetupAttachment(RootComponent);
@@ -134,24 +117,8 @@ ATableIconWidgetActor::ATableIconWidgetActor()
WidgetInteraction->SetupAttachment(RootComponent);
WidgetInteraction->InteractionSource = EWidgetInteractionSource::Custom;
WidgetInteraction->InteractionDistance = 10.f;
}
void ATableIconWidgetActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (!bFaceCamera) return;
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());
SetWidgetClass(LabelWidgetClass);
}
void ATableIconWidgetActor::SetWidgetClass(TSubclassOf<UUserWidget> InWidgetClass)
@@ -164,4 +131,12 @@ void ATableIconWidgetActor::SetWidgetClass(TSubclassOf<UUserWidget> InWidgetClas
UUserWidget* ATableIconWidgetActor::GetWidget() const
{
return WidgetComponent->GetUserWidgetObject();
}
}
//--------------------------------------------------------------------------------------------
void ATableIconLabelActor::UpdateLabel(const FVector& WorldLocation, float Value)
{
SetActorLocation(WorldLocation);
SetLabelText(FString::Printf(TEXT("%.1f%s"), Value, *UnitSuffix));
}
+15 -46
View File
@@ -26,53 +26,22 @@ protected:
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
float UEUnitsPerUnit = 100.f;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite) float UEUnitsPerUnit = 10.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)
bool bRequiresSecondHand = false;
UPROPERTY(EditDefaultsOnly)
bool bExtendable = false;
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();
UFUNCTION(BlueprintCallable, BlueprintNativeEvent) void InitAction(AHandActor* Hand, const FGestureBinding& NewBinding, float NewUEUnitsPerUnit, float TableHeight);
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();
};
@@ -24,34 +24,25 @@ protected:
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Scale")
float UEUnitsPerUnit = 100.f; // set via calibration rectangle
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Scale") float UEUnitsPerUnit = 10.f; // set via calibration rectangle
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Scale") float TableHeight = -650.f;
UPROPERTY(BlueprintReadOnly) AHandManager* HandManager;
UPROPERTY(BlueprintReadOnly)
AHandManager* HandManager;
UFUNCTION(BlueprintCallable, Category = "Gesture")
void EvaluateHands();
UFUNCTION(BlueprintCallable, Category = "Gesture") void EvaluateHands();
UFUNCTION()
void OnHandAdded(AHandActor* Hand);
UFUNCTION() void OnHandAdded(AHandActor* Hand);
UFUNCTION()
void OnHandRemoved(AHandActor* Hand);
UFUNCTION() void OnHandRemoved(AHandActor* Hand);
UPROPERTY(EditDefaultsOnly, Category = "Gesture")
FString BindingConfigPath; // path to the per-game JSON
UPROPERTY(EditDefaultsOnly, Category = "Gesture") FString BindingConfigPath; // path to the per-game JSON
// Loaded from JSON — gesture name -> definition object
UPROPERTY(BlueprintReadOnly, Category = "Gesture")
TArray<UGestureDefinition*> GestureDefinitions;
UPROPERTY(BlueprintReadOnly, Category = "Gesture") TArray<UGestureDefinition*> GestureDefinitions;
UPROPERTY(BlueprintReadOnly, Category = "Gesture")
TArray<AGestureAction*> ActiveGestureActions;
UPROPERTY(BlueprintReadOnly, Category = "Gesture") TArray<AGestureAction*> ActiveGestureActions;
// Populated on BeginPlay from JSON
UPROPERTY(BlueprintReadOnly, Category = "Gesture")
TArray<FGestureBinding> GestureBindings;
UPROPERTY(BlueprintReadOnly, Category = "Gesture") TArray<FGestureBinding> GestureBindings;
void LoadBindings(const FString& FilePath);
void Initialize(UDataTable* InGestureTable, UDataTable* InActionTable);
@@ -59,19 +50,15 @@ public:
void BuildActionMap();
// Populated on BeginPlay from the tables
UPROPERTY(BlueprintReadOnly, Category = "Gesture")
TMap<FName, UGestureDefinition*> DefinitionMap;
UPROPERTY(BlueprintReadOnly, Category = "Gesture") TMap<FName, UGestureDefinition*> DefinitionMap;
UPROPERTY(BlueprintReadOnly, Category = "Gesture")
TMap<FName, TSubclassOf<AGestureAction>> ActionClassMap;
UPROPERTY(BlueprintReadOnly, Category = "Gesture") TMap<FName, TSubclassOf<AGestureAction>> ActionClassMap;
private:
//
UPROPERTY(EditDefaultsOnly, Category = "Gesture")
UDataTable* GestureDefinitionTable;
UPROPERTY(EditDefaultsOnly, Category = "Gesture") UDataTable* GestureDefinitionTable;
UPROPERTY(EditDefaultsOnly, Category = "Gesture")
UDataTable* GestureActionTable;
UPROPERTY(EditDefaultsOnly, Category = "Gesture") UDataTable* GestureActionTable;
void CheckGestureDefinitions(TArray<AHandActor*>& Hands);
+16 -37
View File
@@ -34,50 +34,29 @@ public:
void SpawnNewHand(const FVector& PalmCenter, const FHandPacketHand& NewHand);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Tracking|Debug")
ADetectionManager* DM;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand") ADetectionManager* DM;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand") TArray<AHandActor*> HandList;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand") TArray<AHandActor*> DeadHands;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand") int32 HandAllocationDistance = FMath::Square(1000);;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand") TSubclassOf<class AHandActor> HandActorClass;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand") TSubclassOf<class AFingerTip> FingerTipClass;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand")
TArray<AHandActor*> HandList;
UPROPERTY(BlueprintAssignable) FOnHandAdded OnHandAdded;
UPROPERTY(BlueprintAssignable) FOnHandRemoved OnHandRemoved;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand")
TArray<AHandActor*> DeadHands;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand")
int32 HandAllocationDistance = FMath::Square(1000);;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand")
TSubclassOf<class AHandActor> HandActorClass;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand")
TSubclassOf<class AFingerTip> FingerTipClass;
UFUNCTION(BlueprintCallable)
void AddDeadHand(AHandActor* DeadHand);
UPROPERTY(BlueprintAssignable)
FOnHandAdded OnHandAdded;
UPROPERTY(BlueprintAssignable)
FOnHandRemoved OnHandRemoved;
UFUNCTION(BlueprintCallable)
TArray<AHandActor*>& GetHandList();
UFUNCTION(BlueprintCallable) void AddDeadHand(AHandActor* DeadHand);
UFUNCTION(BlueprintCallable, BlueprintPure) TArray<AHandActor*>& GetHandList();
//Gestures
UPROPERTY(VisibleAnywhere, Category = "Gesture")
UGestureManagerComponent* GestureManager;
UPROPERTY(VisibleAnywhere, Category = "Gesture") UGestureManagerComponent* GestureManager;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gesture") UDataTable* GestureDefinitionTable;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gesture") UDataTable* GestureActionTable;
const TArray<AHandActor*>& GetHandList() const { return HandList; }
// GestureManager.h — add these
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gesture")
UDataTable* GestureDefinitionTable;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gesture")
UDataTable* GestureActionTable;
//Gesture Actions, Icons
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Scale") float UEUnitsPerUnit = 10.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Scale") float TableHeight = -650.f;
private:
void RemoveDeadHands();
+28 -10
View File
@@ -6,6 +6,8 @@
#include "GestureAction.h"
#include "MyGestureActions.generated.h"
class ATableIconLineActor;
class ATableIconLabelActor;
/**
*
*/
@@ -14,23 +16,39 @@ class TABLE_API AMyGestureActions : public AGestureAction
{
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()
class TABLE_API ARulerAction : public AGestureAction
UCLASS(Blueprintable)
class TABLE_API AGestureAction_Ruler : public AGestureAction
{
GENERATED_BODY()
GENERATED_BODY()
public:
void UpdateAction_Implementation() override;
void AbortAction_Implementation() override;
void AddExtension_Implementation(AGestureAction* OtherAction) override;
void EndGesture_Implementation() override;
void InitAction_Implementation(AHandActor* Hand, const FGestureBinding& NewBinding, float NewUEUnitsPerUnit) override;
// Set these in BP defaults — point at your BP subclasses
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Ruler") TSubclassOf<ATableIconLineActor> LineIconClass;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Ruler") TSubclassOf<ATableIconLabelActor> LabelIconClass;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Ruler") FString UnitSuffix = TEXT("\"");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ruler") TArray<FVector> RulerPoints;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ruler") 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:
float GetSnappedInches(float RawDistance) const;
UPROPERTY() ATableIconLineActor* LineIcon = nullptr;
UPROPERTY() ATableIconLabelActor* LabelIcon = nullptr;
UPROPERTY() FVector StartPoint;
float GetSnappedUnits(float RawDistance) const;
};
+27 -47
View File
@@ -11,15 +11,6 @@
class UWidgetComponent;
class UWidgetInteractionComponent;
/**
*
*/
UCLASS()
class TABLE_API AMyTableIconActors : public ATableIconActor
{
GENERATED_BODY()
};
UCLASS(Blueprintable)
@@ -30,36 +21,23 @@ class TABLE_API ATableIconLineActor : public ATableIconActor
public:
ATableIconLineActor();
UFUNCTION(BlueprintCallable)
void SetEndpoints(const FVector& Start, const FVector& End);
UFUNCTION(BlueprintCallable) void SetPoints(const TArray<FVector>& InPoints);
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)
FLinearColor LineColor = FLinearColor::White;
// 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;
protected:
UPROPERTY(BlueprintReadOnly, Category = "Line") TArray<FVector> Points;
private:
UPROPERTY()
UProceduralMeshComponent* MeshComponent;
UPROPERTY() UProceduralMeshComponent* MeshComponent;
void BuildLineMesh(const FVector& Start, const FVector& End);
void BuildTickMesh(const FVector& Start, const FVector& End);
FVector LastStart;
FVector LastEnd;
void BuildLineMesh();
};
//--------------------------------------------------------------------------------------------
UCLASS(Blueprintable)
@@ -70,24 +48,26 @@ class TABLE_API ATableIconWidgetActor : public ATableIconActor
public:
ATableIconWidgetActor();
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
UWidgetComponent* WidgetComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly) UWidgetComponent* WidgetComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly) UWidgetInteractionComponent* WidgetInteraction;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly) TSubclassOf<UUserWidget> LabelWidgetClass;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
UWidgetInteractionComponent* WidgetInteraction;
UFUNCTION(BlueprintCallable) void SetWidgetClass(TSubclassOf<UUserWidget> InWidgetClass);
UFUNCTION(BlueprintCallable, BlueprintPure) UUserWidget* GetWidget() const;
// 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;
UCLASS(Blueprintable)
class TABLE_API ATableIconLabelActor : public ATableIconWidgetActor
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Label") FString UnitSuffix = TEXT("\"");
UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category = "Label") void SetLabelText(const FString& Text);
UFUNCTION(BlueprintCallable, Category = "Label") void UpdateLabel(const FVector& WorldLocation, float Value);
protected:
virtual void Tick(float DeltaTime) override;
};
+6 -6
View File
@@ -6,6 +6,8 @@
#include "GameFramework/Actor.h"
#include "TableIconActor.generated.h"
class AGestureAction;
UCLASS()
class TABLE_API ATableIconActor : public AActor
{
@@ -21,16 +23,14 @@ protected:
public:
// If true, GestureAction will not destroy this on EndGesture
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
bool bPersistOnActionEnd = false;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite) bool bPersistOnActionEnd = false;
UPROPERTY(EditAnywhere) AGestureAction* OwningAction;
// Called by GestureAction on end — BP overrides this for fade/lock behavior
UFUNCTION(BlueprintNativeEvent)
void OnActionEnded();
UFUNCTION(BlueprintNativeEvent) void OnActionEnded();
// Called by GestureAction every tick
UFUNCTION(BlueprintNativeEvent)
void OnActionUpdated();
UFUNCTION(BlueprintNativeEvent) void OnActionUpdated();
};