first commit

This commit is contained in:
DuOtto
2026-04-28 13:17:01 +02:00
commit c7c37b339c
106 changed files with 6310 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;
public class TableTarget : TargetRules
{
public TableTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Game;
DefaultBuildSettings = BuildSettingsVersion.V6;
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7;
ExtraModuleNames.Add("Table");
}
}
@@ -0,0 +1,112 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "ContentNodeWidget.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "IImageWrapper.h"
#include "IImageWrapperModule.h"
#include "Engine/Texture2D.h"
FString UContentNodeWidget::ResolvePath(const FString& RelativePath)
{
//return FPaths::ConvertRelativePathToFull(BaseFolderPath + RelativePath);
FString FullPath = FPaths::ConvertRelativePathToFull(
FPaths::Combine(BaseFolderPath, RelativePath)
);
return FullPath;
}
UTexture2D* UContentNodeWidget::LoadTexture(const FString& RelativePath)
{
FString FullPath = ResolvePath(RelativePath);
return LoadTextureFromPath(FullPath); // see below
}
FString UContentNodeWidget::LoadTextFile(const FString& RelativePath)
{
FString FullPath = ResolvePath(RelativePath);
FString Result;
if (!FFileHelper::LoadFileToString(Result, *FullPath))
UE_LOG(LogTemp, Warning, TEXT("ContentNodeWidget: failed to load text: %s"), *FullPath);
return Result;
}
TArray<UTexture2D*> UContentNodeWidget::LoadPDFPages(const FString& RelativePath)
{
TArray<UTexture2D*> Pages;
// Expects PNGs named: rulebook_p01.png, rulebook_p02.png etc.
// alongside the .pdf — generated by your Python converter offline
FString BaseName = FPaths::GetBaseFilename(RelativePath);
FString Folder = FPaths::GetPath(ResolvePath(RelativePath));
TArray<FString> PageFiles;
IFileManager::Get().FindFiles(PageFiles, *Folder, TEXT("*.png"));
PageFiles.Sort(); // ensures p01, p02, p03 order
for (const FString& PageFile : PageFiles)
{
// Only grab files that belong to this PDF by checking prefix
if (!PageFile.StartsWith(BaseName)) continue;
UTexture2D* Tex = LoadTextureFromPath(Folder / PageFile);
if (Tex) {
Tex->CompressionSettings = TC_EditorIcon; // or TC_UserInterface2D if available
Tex->MipGenSettings = TMGS_NoMipmaps;
Tex->Filter = TF_Nearest; // or TF_Default if too sharp
Tex->SRGB = true;
Tex->UpdateResource();
Pages.Add(Tex);
}
}
return Pages;
}
// Internal helper — loads any PNG/JPG from an absolute path at runtime
UTexture2D* UContentNodeWidget::LoadTextureFromPath(const FString& FullPath)
{
TArray<uint8> FileData;
if (!FFileHelper::LoadFileToArray(FileData, *FullPath))
{
UE_LOG(LogTemp, Warning, TEXT("ContentNodeWidget: failed to read file: %s"), *FullPath);
return nullptr;
}
IImageWrapperModule& ImageWrapperModule =
FModuleManager::LoadModuleChecked<IImageWrapperModule>(TEXT("ImageWrapper"));
EImageFormat Format = ImageWrapperModule.DetectImageFormat(FileData.GetData(), FileData.Num());
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(Format);
if (!ImageWrapper.IsValid() || !ImageWrapper->SetCompressed(FileData.GetData(), FileData.Num()))
{
UE_LOG(LogTemp, Warning, TEXT("ContentNodeWidget: failed to decode image: %s"), *FullPath);
return nullptr;
}
TArray<uint8> RawData;
ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, RawData);
UTexture2D* Texture = UTexture2D::CreateTransient(
ImageWrapper->GetWidth(),
ImageWrapper->GetHeight(),
PF_B8G8R8A8
);
void* TextureData = Texture->GetPlatformData()->Mips[0].BulkData.Lock(LOCK_READ_WRITE);
FMemory::Memcpy(TextureData, RawData.GetData(), RawData.Num());
Texture->GetPlatformData()->Mips[0].BulkData.Unlock();
Texture->UpdateResource();
return Texture;
}
@@ -0,0 +1,250 @@
#include "DetectionManager.h"
#include "DrawDebugHelpers.h"
#include "Sockets.h"
#include "SocketSubsystem.h"
#include "Common/UdpSocketBuilder.h"
#include "Common/UdpSocketReceiver.h"
#include "Networking.h"
// MediaPipe Hands connections (20 bones)
const int32 ADetectionManager::HandConnections[20][2] = {
{0,1},{1,2},{2,3},{3,4},
{0,5},{5,6},{6,7},{7,8},
{0,9},{9,10},{10,11},{11,12},
{0,13},{13,14},{14,15},{15,16},
{0,17},{17,18},{18,19},{19,20}
};
ADetectionManager::ADetectionManager()
{
PrimaryActorTick.bCanEverTick = true;
}
void ADetectionManager::BeginPlay()
{
Super::BeginPlay();
if (bStartReceiverOnBeginPlay)
{
StartUdpReceiver();
}
}
void ADetectionManager::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
StopUdpReceiver();
Super::EndPlay(EndPlayReason);
}
void ADetectionManager::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void ADetectionManager::StartUdpReceiver()
{
if (Socket)
{
return;
}
const FIPv4Endpoint Endpoint(FIPv4Address::Any, ListenPort);
Socket = FUdpSocketBuilder(TEXT("HandTrackingUdpSocket"))
.AsNonBlocking()
.AsReusable()
.BoundToEndpoint(Endpoint)
.WithReceiveBufferSize(2 * 1024 * 1024);
if (!Socket)
{
UE_LOG(LogTemp, Error, TEXT("[DetectionManager] Failed to create UDP socket on port %d"), ListenPort);
return;
}
const FTimespan ThreadWaitTime = FTimespan::FromMilliseconds(2);
Receiver = MakeShared<FUdpSocketReceiver>(Socket, ThreadWaitTime, TEXT("HandTrackingUdpReceiver"));
Receiver->OnDataReceived().BindUObject(this, &ADetectionManager::OnUdpPacketReceived);
Receiver->Start();
UE_LOG(LogTemp, Log, TEXT("[DetectionManager] UDP receiver started on port %d"), ListenPort);
}
void ADetectionManager::StopUdpReceiver()
{
if (Receiver.IsValid())
{
Receiver->Stop();
Receiver.Reset();
}
if (Socket)
{
Socket->Close();
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(Socket);
Socket = nullptr;
}
UE_LOG(LogTemp, Log, TEXT("[DetectionManager] UDP receiver stopped"));
}
void ADetectionManager::OnUdpPacketReceived(
const TSharedPtr<FArrayReader, ESPMode::ThreadSafe>& ArrayReader,
const FIPv4Endpoint& EndPt
)
{
if (!ArrayReader.IsValid() || ArrayReader->Num() <= 0)
{
return;
}
TArray<uint8> Bytes;
Bytes.SetNumUninitialized(ArrayReader->Num());
FMemory::Memcpy(Bytes.GetData(), ArrayReader->GetData(), ArrayReader->Num());
int32 Seq = 0;
uint64 TsMs = 0;
TArray<FHandPacketHand> Hands;
if (!ParseHandPacket(Bytes, Seq, TsMs, Hands))
{
return;
}
{
FScopeLock Lock(&DataMutex);
LatestSeq = Seq;
LatestTimestampMs = TsMs;
LatestHands = MoveTemp(Hands);
bHasNewData = true;
}
}
// ---- Little-endian readers ----
static uint32 ReadU32LE(const uint8* Ptr)
{
return (uint32)Ptr[0] | ((uint32)Ptr[1] << 8) | ((uint32)Ptr[2] << 16) | ((uint32)Ptr[3] << 24);
}
static uint64 ReadU64LE(const uint8* Ptr)
{
uint64 v = 0;
for (int i = 0; i < 8; ++i) v |= ((uint64)Ptr[i] << (8 * i));
return v;
}
static float ReadF32LE(const uint8* Ptr)
{
uint32 u = ReadU32LE(Ptr);
float f;
FMemory::Memcpy(&f, &u, sizeof(float));
return f;
}
bool ADetectionManager::ParseHandPacket(const TArray<uint8>& Bytes, int32& OutSeq, uint64& OutTsMs, TArray<FHandPacketHand>& OutHands)
{
// Header: <4s B I Q B
// per hand: <B B B f + point_count*3 float32
const int32 MinHeaderSize = 4 + 1 + 4 + 8 + 1;
if (Bytes.Num() < MinHeaderSize)
return false;
const uint8* Ptr = Bytes.GetData();
const uint8* End = Ptr + Bytes.Num();
// magic
if (!(Ptr[0] == 'H' && Ptr[1] == 'A' && Ptr[2] == 'N' && Ptr[3] == 'D'))
return false;
Ptr += 4;
const uint8 Version = *Ptr++;
// Accept either version 1 (old) or 2 (new), or enforce one
//if (Version != 2) // set to 2 if you bump Python VERSION
// return false;
OutSeq = (int32)ReadU32LE(Ptr); Ptr += 4;
OutTsMs = ReadU64LE(Ptr); Ptr += 8;
const uint8 HandCount = *Ptr++;
OutHands.Reset();
OutHands.Reserve(HandCount);
for (uint8 hi = 0; hi < HandCount; ++hi)
{
// Need at least: hand_id + point_count + handedness + confidence
if (Ptr + 1 + 1 + 1 + 4 > End) return false;
const uint8 HandId = *Ptr++;
const uint8 PointCount = *Ptr++;
const uint8 HandednessByte = *Ptr++;
const float Confidence = ReadF32LE(Ptr); Ptr += 4;
const int64 NeedBytes = (int64)PointCount * 3 * 4;
if (Ptr + NeedBytes > End) return false;
if (PointCount != 21)
{
Ptr += NeedBytes;
continue;
}
FHandPacketHand Hand;
Hand.HandId = (int32)HandId;
Hand.Handedness = (HandednessByte == 1) ? EHandedness::Left :
(HandednessByte == 2) ? EHandedness::Right :
EHandedness::Unknown;
Hand.Confidence = Confidence;
Hand.Points.SetNum(PointCount);
for (int32 pi = 0; pi < PointCount; ++pi)
{
const float X = ReadF32LE(Ptr); Ptr += 4;
const float Y = ReadF32LE(Ptr); Ptr += 4;
const float Z = ReadF32LE(Ptr); Ptr += 4;
Hand.Points[pi] = FVector(X, Y, -Z);
}
OutHands.Add(MoveTemp(Hand));
}
return true;
}
float ADetectionManager::MapDepthToRadiusCm(float Z) const
{
if (!bRadiusFromDepth)
return BasePointRadiusCm;
const float Zc = FMath::Clamp(Z, DepthNear, DepthFar);
const float T = (Zc - DepthNear) / FMath::Max(1.0f, (DepthFar - DepthNear)); // 0..1
return FMath::Lerp(RadiusNearCm, RadiusFarCm, T);
}
bool ADetectionManager::ConsumeLatestHands(TArray<FHandPacketHand>& OutHands, int32& OutSeq, int64& OutTimestampMs)
{
FScopeLock Lock(&DataMutex);
if (!bHasNewData)
{
OutHands.Reset();
OutSeq = 0;
OutTimestampMs = 0;
return false;
}
// Atomically "consume" the latest frame
bHasNewData = false;
OutSeq = LatestSeq;
OutTimestampMs = (int64)LatestTimestampMs;
OutHands = MoveTemp(LatestHands); // takes ownership, leaves LatestHands empty
return OutHands.Num() > 0;
}
+27
View File
@@ -0,0 +1,27 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "FingerTip.h"
// Sets default values
AFingerTip::AFingerTip()
{
// 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;
}
// Called when the game starts or when spawned
void AFingerTip::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AFingerTip::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
@@ -0,0 +1,46 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "GestureAction.h"
#include "HandActor.h"
// Sets default values
AGestureAction::AGestureAction()
{
// 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;
}
// Called when the game starts or when spawned
void AGestureAction::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AGestureAction::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void AGestureAction::UpdateAction()
{
Destroy();
}
void AGestureAction::AbortAction()
{
}
void AGestureAction::AddExtension(AHandActor* OtherHand)
{
HandB = OtherHand;
}
void AGestureAction::EndGesture()
{
Destroy();
}
@@ -0,0 +1,5 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "GestureData.h"
@@ -0,0 +1,27 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "GestureDefinition.h"
#include "HandActor.h"
float UGestureDefinition::Evaluate(AHandActor* Hand) const
{
return 0.f;
}
float UGesture_PinchPoint::Evaluate(AHandActor* Hand) const
{
float Pinch = GetPinchScore(Hand, EHandFinger::Thumb, EHandFinger::Index);
float IndexExtended = GetFingerExtendedScore(Hand, EHandFinger::Index);
float OthersCurled = GetFingersAverage(
Hand,
{ EHandFinger::Middle, EHandFinger::Ring, EHandFinger::Pinky },
false
);
return
Pinch * 0.5f +
IndexExtended * 0.3f +
OthersCurled * 0.2f;
}
@@ -0,0 +1,252 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "GestureManagerComponent.h"
#include "HandManager.h"
#include "GestureDefinition.h"
#include "GestureAction.h"
#include "HandActor.h"
// Sets default values for this component's properties
UGestureManagerComponent::UGestureManagerComponent()
{
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = false;
HandManager = Cast<AHandManager>(GetOwner());
}
// Called when the game starts
void UGestureManagerComponent::BeginPlay()
{
Super::BeginPlay();
HandManager->OnHandAdded.AddDynamic(this, &UGestureManagerComponent::OnHandAdded);
HandManager->OnHandRemoved.AddDynamic(this, &UGestureManagerComponent::OnHandRemoved);
}
void UGestureManagerComponent::EvaluateHands()
{
TArray<AHandActor*>& Hands = HandManager->HandList;
for (AHandActor* Hand : Hands) Hand->bUsedThisTick = false;
CheckGestureDefinitions(Hands);
for (AHandActor* Hand : Hands)
{
if (Hand->bUsedThisTick) continue;
switch (Hand->Phase)
{
case EGesturePhase::Started:
TriggerAction(Hand);
break;
case EGesturePhase::Ended:
Hand->ResetGesture();
EndGesture();
break;
}
}
UpdateOngoingAction();
}
void UGestureManagerComponent::OnHandAdded(AHandActor* Hand)
{
Hand->ResetGesture();
}
void UGestureManagerComponent::OnHandRemoved(AHandActor* Hand)
{
Hand->PairedHand->PairedHand = nullptr;
Hand->ResetGesture();
}
void UGestureManagerComponent::CheckGestureDefinitions(TArray<AHandActor*>& Hands)
{
const float MinScoreThreshold = 0.5f;
for (AHandActor* Hand : Hands) {
float BestScore = 0.f;
FString BestGesture = "";
for (UGestureDefinition* Gesture : GestureDefinitions) {
float Score = Gesture->Evaluate(Hand);
if (Score > BestScore)
{
BestScore = Score;
BestGesture = Gesture->GestureName;
}
}
if (BestScore < MinScoreThreshold)
{
if (Hand->Phase != EGesturePhase::None)
{
Hand->FailFrames++;
if (Hand->FailFrames >= FailThreshold)
{
Hand->Phase = EGesturePhase::Ended;
}
}
continue;
}
switch (Hand->Phase)
{
case EGesturePhase::None:
Hand->ActiveGestureName = BestGesture;
Hand->Phase = EGesturePhase::Candidate;
Hand->CandidateFrames = 1;
Hand->FailFrames = 0;
break;
case EGesturePhase::Candidate:
if (Hand->ActiveGestureName != BestGesture)
{
Hand->FailFrames++;
break;
}
Hand->CandidateFrames++;
Hand->FailFrames = 0;
if (Hand->CandidateFrames >= CandidateThreshold)
Hand->Phase = EGesturePhase::Started;
break;
case EGesturePhase::Ongoing:
if (Hand->ActiveGestureName != BestGesture)
{
Hand->FailFrames++;
if (Hand->FailFrames >= FailThreshold)
{
Hand->Phase = EGesturePhase::Ended;
}
}
else
{
Hand->FailFrames = 0;
}
break;
}
if (Hand->FailFrames >= FailThreshold)
Hand->Phase = EGesturePhase::Ended;
}
}
void UGestureManagerComponent::TriggerAction(AHandActor* Hand)
{
if (!Hand->bUsedThisTick) return;
if (Hand->IsPaired())
{
AHandActor* Other = Hand->PairedHand;
for (const FGestureBinding& Binding : GestureBindings)
{
const bool bLeftMatch = (Hand->Handedness == EHandedness::Left && Hand->ActiveGestureName == Binding.LeftGesture &&
Other->ActiveGestureName == Binding.RightGesture);
const bool bRightMatch = (Hand->Handedness == EHandedness::Right && Hand->ActiveGestureName == Binding.RightGesture &&
Other->ActiveGestureName == Binding.LeftGesture);
if (!(bLeftMatch || bRightMatch))
continue;
// both must be ready
if (Other->Phase != EGesturePhase::Started)
continue;
// mark used
Hand->bUsedThisTick = true;
Other->bUsedThisTick = true;
Hand->Phase = EGesturePhase::Ongoing;
Other->Phase = EGesturePhase::Ongoing;
// spawn action (centralized factory recommended)
SpawnGestureAction(Binding.ActionName, Hand, Other);
return;
}
}
for (const FGestureBinding& Binding : GestureBindings)
{
const bool bMatch =
(Hand->Handedness == EHandedness::Left && Binding.LeftGesture == Hand->ActiveGestureName) ||
(Hand->Handedness == EHandedness::Right && Binding.RightGesture == Hand->ActiveGestureName);
if (!bMatch)
continue;
// if paired hand is actively running something, allow extension check later
if (Hand->IsPaired() && Hand->PairedHand && Hand->PairedHand->Phase == EGesturePhase::Ongoing)
{
for (const FExtendedGestureBinding& Ext : Binding.ExentdedGestures)
{
if (Ext.GestureName != Hand->ActiveGestureName)
continue;
if (Ext.GestureAction == Binding.ActionName)
{
// extend current action
ExtendGestureAction(Binding.ActionName, Hand);
Hand->bUsedThisTick = true;
return;
}
else
{
// branch: spawn new action linked to existing one
SpawnGestureActionWithParent(Ext.GestureAction, Hand, Hand->PairedHand);
Hand->bUsedThisTick = true;
return;
}
}
}
// normal one-hand action
Hand->bUsedThisTick = true;
SpawnGestureAction(Binding.ActionName, Hand, nullptr);
return;
}
}
void UGestureManagerComponent::UpdateOngoingAction()
{
for (AGestureAction* Action : ActiveGestureActions)
{
if (!Action) continue;
Action->UpdateAction();
}
}
void UGestureManagerComponent::SpawnGestureAction(FString ActionName, AHandActor* A, AHandActor* B)
{
}
void UGestureManagerComponent::ExtendGestureAction(FString ActionName, AHandActor* Hand)
{
}
void UGestureManagerComponent::SpawnGestureActionWithParent(FString ActionName, AHandActor* Hand, AHandActor* ParentHand)
{
}
+148
View File
@@ -0,0 +1,148 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "HandActor.h"
#include "HandManager.h"
#include "FingerTip.h"
#include "DrawDebugHelpers.h"
static const int32 HandBones[][2] = {
{0,1},{1,2},{2,3},{3,4},
{0,5},{5,6},{6,7},{7,8},
{0,9},{9,10},{10,11},{11,12},
{0,13},{13,14},{14,15},{15,16},
{0,17},{17,18},{18,19},{19,20},
{5,9},{9,13},{13,17}
};
static const int32 FingerTipBones[] = {
4, 8, 12, 16, 20
};
static const int32 HandBoneCount = sizeof(HandBones) / sizeof(HandBones[0]);
static const float Life = 0.05f;
// Sets default values
AHandActor::AHandActor()
{
// 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;
}
// Called when the game starts or when spawned
void AHandActor::BeginPlay()
{
Super::BeginPlay();
GetWorld()->GetTimerManager().SetTimer(DeathTimer, this, &AHandActor::HandDeath, DeathTime, true);
}
// Called every frame
void AHandActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
DrawDebugCoordinateSystem(GetWorld(), FVector::ZeroVector, FRotator::ZeroRotator, 100.0f, false, 0.1f, 0, 2.0f);
//SetActorLocation(GetPalmCenter());
if (Points.Num() != 21) return;
for(int32 i = 0; i < HandBoneCount; ++i)
{
const int32 BoneA = HandBones[i][0];
const int32 BoneB = HandBones[i][1];
DrawDebugLine(
GetWorld(),
Points[BoneA],
Points[BoneB],
FColor::Red,
false,
Life,
0,
1.5f
);
}
for (const FVector& P : Points) {
DrawDebugSphere(GetWorld(), P, 2.0f, 8, FColor::Green, false, Life);
}
}
FVector AHandActor::GetPalmCenter()
{
return ((Points[0] + Points[5] + Points[9] + Points[13] + Points[17]) / 5.0f);
}
EHandedness AHandActor::GetHandedness()
{
return Handedness;
}
void AHandActor::InitHand_Implementation(const FHandPacketHand& NewHand, AHandManager* NewHandManager, TSubclassOf<class AFingerTip> FingerTipClass)
{
Points = NewHand.Points;
Handedness = NewHand.Handedness;
Confidence = NewHand.Confidence;
HandId = NewHand.HandId;
HandManager = NewHandManager;
//Spawn Finger Tips
for (int32 FingerTipPoint : FingerTipBones)
{
FTransform SpawnTransform;
SpawnTransform.SetLocation(GetPalmCenter());
SpawnTransform.SetRotation(FQuat::Identity);
SpawnTransform.SetScale3D(FVector::OneVector);
FActorSpawnParameters Params;
Params.Owner = this;
Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
AFingerTip* NewFinger = GetWorld()->SpawnActor<AFingerTip>(
FingerTipClass,
SpawnTransform,
Params
);
FingerTipActorList.Add(NewFinger);
}
}
void AHandActor::UpdateHand(const FHandPacketHand& NewHand)
{
GetWorld()->GetTimerManager().ClearTimer(DeathTimer);
GetWorld()->GetTimerManager().SetTimer(DeathTimer, this, &AHandActor::HandDeath, DeathTime, true);
Points = NewHand.Points;
Handedness = NewHand.Handedness;
Confidence = NewHand.Confidence;
HandId = NewHand.HandId;
//UE_LOG(LogTemp, Error, TEXT("Update Hand"));
for (int32 n = 0; n < 5; n++)
{
FVector f = Points[FingerTipBones[n]];
FingerTipActorList[n]->SetActorLocation(f);
}
}
void AHandActor::HandDeath()
{
for (AFingerTip* FingerTip : FingerTipActorList) {
FingerTip->Destroy();
}
if (IsValid(HandManager))
{
HandManager->AddDeadHand(this);
}
}
+128
View File
@@ -0,0 +1,128 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "HandManager.h"
#include "GestureManagerComponent.h"
#include "HandActor.h"
// Sets default values
AHandManager::AHandManager()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
GestureManager = CreateDefaultSubobject<UGestureManagerComponent>(TEXT("GestureManager"));
}
// Called when the game starts or when spawned
void AHandManager::BeginPlay()
{
Super::BeginPlay();
if (!IsValid(HandActorClass))
{
HandActorClass = AHandActor::StaticClass();
}
}
// Called every frame
void AHandManager::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (!IsValid(DM)) return;
RemoveDeadHands();
UpdateHands();
GestureManager->EvaluateHands();
}
void AHandManager::SpawnNewHand(const FVector& PalmCenter, const FHandPacketHand& NewHand)
{
FTransform SpawnTransform;
SpawnTransform.SetLocation(PalmCenter);
SpawnTransform.SetRotation(FQuat::Identity);
SpawnTransform.SetScale3D(FVector::OneVector);
FActorSpawnParameters Params;
Params.Owner = this;
Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
AHandActor* NewHandActor = GetWorld()->SpawnActor<AHandActor>(
HandActorClass,
SpawnTransform,
Params
);
if (NewHandActor)
{
HandList.Add(NewHandActor);
NewHandActor->InitHand(NewHand, this, FingerTipClass);
}
OnHandAdded.Broadcast(NewHandActor);
}
void AHandManager::AddDeadHand(AHandActor* DeadHand)
{
DeadHands.Add(DeadHand);
}
TArray<AHandActor*> AHandManager::GetHandList()
{
return HandList;
}
void AHandManager::RemoveDeadHands()
{
for (AHandActor* Hand : DeadHands)
{
if (IsValid(Hand))
{
HandList.Remove(Hand);
OnHandRemoved.Broadcast(Hand);
Hand->Destroy();
}
}
DeadHands.Empty();
}
void AHandManager::UpdateHands()
{
TArray<FHandPacketHand> NewHands;
int32 OutSeq;
int64 OutTimestampMs;
if (DM->ConsumeLatestHands(NewHands, OutSeq, OutTimestampMs)) {
bool HandFound;
for (const FHandPacketHand& NewHand : NewHands)
{
const TArray<FVector>& Points = NewHand.Points;
HandFound = false;
if (Points.Num() < 21) continue;
const FVector PalmCenter = (Points[0] + Points[5] + Points[9] + Points[13] + Points[17]) / 5.0f;
for (AHandActor* Hand : HandList)
{
if (!IsValid(Hand)) continue;
if (Hand->GetHandedness() != NewHand.Handedness) continue;
float DistSq = FVector::DistSquared(Hand->GetPalmCenter(), PalmCenter);
if (DistSq > HandAllocationDistance) continue;
Hand->UpdateHand(NewHand);
HandFound = true;
break;
}
if (!HandFound)
{
SpawnNewHand(PalmCenter, NewHand);
}
}
}
}
@@ -0,0 +1,11 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "JsonSelectionOption.h"
FString UJsonSelectionOption::GetOptionDisplayName()
{
// Gets the folder containing the json, e.g. "Tyranid"
FString Folder = FPaths::GetCleanFilename(FPaths::GetPath(OptionValue));
return Folder;
}
@@ -0,0 +1,115 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "UserHUDWidget.h"
#include "Misc/Paths.h"
#include "Misc/FileHelper.h"
#include "HAL/FileManager.h"
TArray<FString> UUserHUDWidget::GetJsonFileNames()
{
TArray<FString> FileNames;
FString FolderPath = ConfigFolderPath;
IFileManager::Get().FindFilesRecursive(
FileNames,
*FolderPath,
TEXT("*.json"),
true, // files
false // directories
);
//for (FString& Name : FileNames) FPaths::GetBaseFilename(Name);
return FileNames;
}
UContentWidgetData* UUserHUDWidget::LoadUserHUDJsonFile(const FString& FullPath)
{
FString JsonString;
// Load the File as a String
if (!FFileHelper::LoadFileToString(JsonString, *FullPath))
{
UE_LOG(LogTemp, Warning, TEXT("LoadUserHUDJsonFile: failed to read %s"), *FullPath);
return nullptr;
}
TSharedPtr<FJsonObject> RootObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
//Convert the Loaded String to a JSON Format
if (!FJsonSerializer::Deserialize(Reader, RootObject) || !RootObject.IsValid())
{
UE_LOG(LogTemp, Warning, TEXT("LoadUserHUDJsonFile: failed to parse JSON at %s"), *FullPath);
return nullptr;
}
return ParseContentData(RootObject, this);
}
UContentWidgetData* UUserHUDWidget::ParseContentData(TSharedPtr<FJsonObject> JsonObject, UObject* Outer)
{
UContentWidgetData* ContentData = NewObject<UContentWidgetData>(Outer);
// WidgetType
JsonObject->TryGetStringField(ContentJsonKeys::Type, ContentData->WidgetType);
// Sources array
const TArray<TSharedPtr<FJsonValue>>* SourcesArray;
if (JsonObject->TryGetArrayField(ContentJsonKeys::Sources, SourcesArray))
{
for (const TSharedPtr<FJsonValue>& SourceValue : *SourcesArray)
{
TSharedPtr<FJsonObject> SourceObj = SourceValue->AsObject();
if (SourceObj.IsValid())
ContentData->DataSources.Add(ParseContentSource(SourceObj));
}
}
// Children array — [{ "name": "Rules", "content": { ... } }, ...]
const TArray<TSharedPtr<FJsonValue>>* ChildrenArray;
if (JsonObject->TryGetArrayField(ContentJsonKeys::SubWidgets, ChildrenArray))
{
for (const TSharedPtr<FJsonValue>& ChildValue : *ChildrenArray)
{
TSharedPtr<FJsonObject> SubWidgetObj = ChildValue->AsObject();
if (!SubWidgetObj.IsValid()) continue;
FString SubWidgetName;
SubWidgetObj->TryGetStringField(ContentJsonKeys::Name, SubWidgetName);
const TSharedPtr<FJsonObject>* ContentObj;
if (SubWidgetObj->TryGetObjectField(ContentJsonKeys::Content, ContentObj))
{
ContentData->SubWidgetNames.Add(SubWidgetName);
// Recurse — pass Node as Outer so GC hierarchy is correct
ContentData->SubWidgets.Add(ParseContentData(*ContentObj, ContentData));
}
}
}
return ContentData;
}
FContentSource UUserHUDWidget::ParseContentSource(TSharedPtr<FJsonObject> SourceObject)
{
FContentSource Source;
FString ContentTypeString;
SourceObject->TryGetStringField(ContentJsonKeys::ContentType, ContentTypeString);
SourceObject->TryGetStringField(ContentJsonKeys::Source, Source.Source);
if (ContentTypeString == TEXT("Image")) Source.ContentType = EContentSourceType::Image;
else if (ContentTypeString == TEXT("PDF")) Source.ContentType = EContentSourceType::PDF;
else if (ContentTypeString == TEXT("TextFile")) Source.ContentType = EContentSourceType::TextFile;
else if (ContentTypeString == TEXT("InlineText")) Source.ContentType = EContentSourceType::InlineText;
else Source.ContentType = EContentSourceType::InlineText;
return Source;
}
@@ -0,0 +1,48 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "UserHUDWidget.h"
#include "ContentNodeWidget.generated.h"
/**
*
*/
UCLASS()
class TABLE_API UContentNodeWidget : public UUserWidget
{
GENERATED_BODY()
public:
// Set this before adding the widget to the viewport
UPROPERTY(BlueprintReadWrite, Category = "Content", Meta = (ExposeOnSpawn = true))
UContentWidgetData* WidgetData;
// Base folder of the loaded army — all relative paths resolve against this
// e.g. "C:/Test/HUDs/Tyranid/"
UPROPERTY(BlueprintReadWrite, Category = "Content", Meta = (ExposeOnSpawn = true))
FString BaseFolderPath;
// Load a single texture from a relative path
UFUNCTION(BlueprintCallable, Category = "Content")
UTexture2D* LoadTexture(const FString& RelativePath);
// Load a .txt file as a string
UFUNCTION(BlueprintCallable, Category = "Content")
FString LoadTextFile(const FString& RelativePath);
// PDF — returns one texture per page (expects pre-rendered PNGs)
// naming convention: basename_p01.png, basename_p02.png ...
UFUNCTION(BlueprintCallable, Category = "Content")
TArray<UTexture2D*> LoadPDFPages(const FString& RelativePath);
// Convenience — full path from relative
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Content")
FString ResolvePath(const FString& RelativePath);
private:
UTexture2D* LoadTextureFromPath(const FString& FullPath);
};
@@ -0,0 +1,125 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "HAL/CriticalSection.h"
#include "Misc/ScopeLock.h"
#include "Serialization/ArrayReader.h" // FArrayReader
#include "Interfaces/IPv4/IPv4Endpoint.h" // FIPv4Endpoint
#include "GestureData.h"
#include "DetectionManager.generated.h"
class FSocket;
class FUdpSocketReceiver;
USTRUCT(BlueprintType)
struct FHandPacketHand
{
GENERATED_BODY()
UPROPERTY()
int32 HandId = -1;
UPROPERTY()
EHandedness Handedness = EHandedness::Unknown;
UPROPERTY()
float Confidence = 0.0f;
UPROPERTY()
TArray<FVector> Points; // size 21
};
UCLASS()
class TABLE_API ADetectionManager : public AActor
{
GENERATED_BODY()
public:
ADetectionManager();
UFUNCTION(BlueprintCallable, Category = "Hands")
bool ConsumeLatestHands(TArray<FHandPacketHand>& OutHands, int32& OutSeq, int64& OutTimestampMs);
protected:
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
public:
virtual void Tick(float DeltaTime) override;
// UDP settings
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Tracking|UDP")
int32 ListenPort = 9000;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Tracking|UDP")
bool bStartReceiverOnBeginPlay = true;
// Debug draw
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Tracking|Debug")
bool bDrawDebugHands = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Tracking|Debug")
bool bDrawDebugBones = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Tracking|Debug")
float UnitScaleToUE = 0.1f; // mm -> cm
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Tracking|Debug")
bool bUseActorTransformAsOrigin = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Tracking|Debug")
bool bRadiusFromDepth = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Tracking|Debug")
float BasePointRadiusCm = 1.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Tracking|Debug")
float DepthNear = 200.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Tracking|Debug")
float DepthFar = 1200.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Tracking|Debug")
float RadiusNearCm = 2.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Tracking|Debug")
float RadiusFarCm = 0.5f;
UPROPERTY(BlueprintReadOnly, Category = "Hand Tracking|Debug")
int32 LastSeq = 0;
UPROPERTY(BlueprintReadOnly, Category = "Hand Tracking|Debug")
int32 LastHandCount = 0;
private:
// Socket + receiver
FSocket* Socket = nullptr;
TSharedPtr<FUdpSocketReceiver> Receiver;
// Thread-safe latest data buffer
FCriticalSection DataMutex;
bool bHasNewData = false;
int32 LatestSeq = 0;
uint64 LatestTimestampMs = 0;
TArray<FHandPacketHand> LatestHands;
private:
void StartUdpReceiver();
void StopUdpReceiver();
// Must match FUdpSocketReceiver delegate signature
void OnUdpPacketReceived(
const TSharedPtr<FArrayReader, ESPMode::ThreadSafe>& ArrayReader,
const FIPv4Endpoint& EndPt
);
bool ParseHandPacket(const TArray<uint8>& Bytes, int32& OutSeq, uint64& OutTsMs, TArray<FHandPacketHand>& OutHands);
float MapDepthToRadiusCm(float Z) const;
static const int32 HandConnections[20][2];
};
+26
View File
@@ -0,0 +1,26 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "FingerTip.generated.h"
UCLASS()
class TABLE_API AFingerTip : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AFingerTip();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
+48
View File
@@ -0,0 +1,48 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "GestureAction.generated.h"
class AHandActor;
UCLASS()
class TABLE_API AGestureAction : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AGestureAction();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
UPROPERTY(BlueprintReadWrite)
TMap<FName, FVector> Anchors;
UPROPERTY(BlueprintReadWrite)
AHandActor* HandA;
UPROPERTY(BlueprintReadWrite)
AHandActor* HandB;
UFUNCTION(BlueprintCallable)
void UpdateAction();
UFUNCTION(BlueprintCallable)
void AbortAction();
UFUNCTION(BlueprintCallable)
void AddExtension(AHandActor* OtherHand);
UFUNCTION(BlueprintCallable)
void EndGesture();
};
+81
View File
@@ -0,0 +1,81 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataTable.h"
#include "GestureData.generated.h"
class AHandActor;
class AGestureAction;
class UGestureDefinition;
UENUM(BlueprintType)
enum class EHandedness : uint8
{
Unknown = 0,
Left = 1,
Right = 2
};
UENUM(BlueprintType)
enum class EGesturePhase : uint8
{
None,
Candidate,
Started,
Ongoing,
Ended
};
// Row struct for DT_GestureDefinitions
USTRUCT(BlueprintType)
struct FGestureDefinitionRow : public FTableRowBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TSubclassOf<UGestureDefinition> DefinitionClass;
};
// Row struct for DT_GestureActions
USTRUCT(BlueprintType)
struct FGestureActionRow : public FTableRowBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TSubclassOf<AGestureAction> ActionClass;
};
USTRUCT(BlueprintType)
struct FExtendedGestureBinding
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly)
FString GestureName;
UPROPERTY(BlueprintReadOnly)
FString GestureAction;
};
// What gets parsed from the JSON
USTRUCT(BlueprintType)
struct FGestureBinding
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly)
FString ActionName;
UPROPERTY(BlueprintReadOnly)
FString LeftGesture;
UPROPERTY(BlueprintReadOnly)
FString RightGesture;
UPROPERTY(BlueprintReadOnly)
TArray<FExtendedGestureBinding> ExentdedGestures;
};
@@ -0,0 +1,146 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "GestureManagerComponent.h"
#include "GestureDefinition.generated.h"
class AHandActor;
UENUM(BlueprintType)
enum class EHandFinger : uint8
{
Thumb,
Index,
Middle,
Ring,
Pinky
};
/**
*
*/
UCLASS()
class TABLE_API UGestureDefinition : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite)
FString GestureName;
UFUNCTION()
virtual float Evaluate(AHandActor* Hand) const;
int32 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 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()
class TABLE_API UGesture_PinchPoint : public UGestureDefinition
{
GENERATED_BODY()
public:
virtual float Evaluate(AHandActor* Hand) const override;
};
@@ -0,0 +1,84 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "GestureData.h"
#include "GestureManagerComponent.generated.h"
UCLASS(ClassGroup = "Gesture", BlueprintType, Blueprintable, ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class TABLE_API UGestureManagerComponent : public UActorComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UGestureManagerComponent();
protected:
// Called when the game starts
virtual void BeginPlay() override;
public:
UPROPERTY(BlueprintReadOnly)
class AHandManager* HandManager;
UFUNCTION(BlueprintCallable, Category = "Gesture")
void EvaluateHands();
UFUNCTION()
void OnHandAdded(AHandActor* Hand);
UFUNCTION()
void OnHandRemoved(AHandActor* Hand);
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<AGestureAction*> ActiveGestureActions;
// Populated on BeginPlay from JSON
UPROPERTY(BlueprintReadOnly, Category = "Gesture")
TArray<FGestureBinding> GestureBindings;
// Populated on BeginPlay from the tables
UPROPERTY(BlueprintReadOnly, Category = "Gesture")
TMap<FString, UGestureDefinition*> DefinitionMap;
UPROPERTY(BlueprintReadOnly, Category = "Gesture")
TMap<FString, TSubclassOf<AGestureAction>> ActionClassMap;
private:
// GestureManager.h — add these
UPROPERTY(EditDefaultsOnly, Category = "Gesture")
UDataTable* GestureDefinitionTable;
UPROPERTY(EditDefaultsOnly, Category = "Gesture")
UDataTable* GestureActionTable;
void CheckGestureDefinitions(TArray<AHandActor*>& Hands);
void TriggerAction(AHandActor* Hand);
void UpdateOngoingAction();
void EndGesture();
void SpawnGestureAction(FString ActionName, AHandActor* A, AHandActor* B);
void ExtendGestureAction(FString ActionName, AHandActor* Hand);
void SpawnGestureActionWithParent(FString ActionName, AHandActor* Hand, AHandActor* ParentHand);
static constexpr int32 CandidateThreshold = 8;
static constexpr int32 FailThreshold = 5;
};
+102
View File
@@ -0,0 +1,102 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "DetectionManager.h"
#include "GestureData.h"
#include "HandActor.generated.h"
class AHandManager;
class AFingerTip;
UCLASS()
class TABLE_API AHandActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AHandActor();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stuff")
int32 HandId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stuff")
EHandedness Handedness;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stuff")
float Confidence;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stuff")
TArray<FVector> Points; // size 21
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stuff")
TArray<AFingerTip*> FingerTipActorList; // size 21
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stuff")
AHandManager* HandManager;
FTimerHandle DeathTimer;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stuff")
int32 DeathTime = 5;
// Gestures
UPROPERTY(BlueprintReadWrite)
EGesturePhase Phase = EGesturePhase::None;
UPROPERTY(BlueprintReadWrite)
FString ActiveGestureName;
UPROPERTY(BlueprintReadWrite)
TMap<FString, FVector> Anchors;
UPROPERTY(BlueprintReadWrite)
int32 CandidateFrames = 0;
UPROPERTY(BlueprintReadWrite)
int32 FailFrames = 0;
UPROPERTY(BlueprintReadOnly)
AHandActor* PairedHand = nullptr;
bool bUsedThisTick = false;
bool IsPaired() const { return PairedHand != nullptr; }
void ResetGesture()
{
Phase = EGesturePhase::None;
ActiveGestureName = "";
CandidateFrames = 0;
FailFrames = 0;
}
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
UFUNCTION(BlueprintCallable)
FVector GetPalmCenter();
UFUNCTION(BlueprintCallable)
EHandedness GetHandedness();
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void InitHand(const FHandPacketHand& NewHand, AHandManager* NewHandManager, TSubclassOf<class AFingerTip> FingerTipClass);
UFUNCTION(BlueprintCallable)
void UpdateHand(const FHandPacketHand& NewHand);
UFUNCTION(BlueprintCallable)
void HandDeath();
};
+77
View File
@@ -0,0 +1,77 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "DetectionManager.h"
#include "HandManager.generated.h"
class AHandActor;
class UGestureManagerComponent;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHandAdded, AHandActor*, Hand);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHandRemoved, AHandActor*, Hand);
UCLASS()
class TABLE_API AHandManager : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AHandManager();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
void SpawnNewHand(const FVector& PalmCenter, const FHandPacketHand& NewHand);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Tracking|Debug")
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;
UFUNCTION(BlueprintCallable)
void AddDeadHand(AHandActor* DeadHand);
UPROPERTY(BlueprintAssignable)
FOnHandAdded OnHandAdded;
UPROPERTY(BlueprintAssignable)
FOnHandRemoved OnHandRemoved;
UFUNCTION(BlueprintCallable)
TArray<AHandActor*> GetHandList();
//Gestures
UPROPERTY(VisibleAnywhere, Category = "Gesture")
UGestureManagerComponent* GestureManager;
const TArray<AHandActor*>& GetHandList() const { return HandList; }
private:
void RemoveDeadHands();
void UpdateHands();
};
@@ -0,0 +1,25 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "JsonSelectionOption.generated.h"
/**
*
*/
UCLASS()
class TABLE_API UJsonSelectionOption : public UUserWidget
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
FString OptionValue;
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "HUD")
FString GetOptionDisplayName();
};
@@ -0,0 +1,19 @@
#pragma once
#pragma once
THIRD_PARTY_INCLUDES_START
#ifdef check
#pragma push_macro("check")
#undef check
#define OPENCV_RESTORE_CHECK_MACRO 1
#endif
#include <opencv2/opencv.hpp>
#ifdef OPENCV_RESTORE_CHECK_MACRO
#pragma pop_macro("check")
#undef OPENCV_RESTORE_CHECK_MACRO
#endif
THIRD_PARTY_INCLUDES_END
+103
View File
@@ -0,0 +1,103 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "UserHUDWidget.generated.h"
/**
*
*/
UCLASS(BlueprintType, Blueprintable)
class TABLE_API UUserHUDWidget : public UUserWidget
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "HUD")
TArray<FString> GetJsonFileNames();
UFUNCTION(BlueprintCallable, Category = "HUD")
UContentWidgetData* LoadUserHUDJsonFile(const FString& FullPath);
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "HUD")
FString ConfigFolderPath = TEXT("C:/git/Table/Test/HUDs");
private:
UContentWidgetData* ParseContentData(TSharedPtr<FJsonObject> JsonObject, UObject* Outer);
FContentSource ParseContentSource(TSharedPtr<FJsonObject> SourceObject);
};
//--------------------------------------------------------------------------------------
namespace ContentJsonKeys
{
const FString Type = TEXT("type");
const FString Sources = TEXT("sources");
const FString SubWidgets = TEXT("subwidgets");
const FString Name = TEXT("name");
const FString Content = TEXT("content");
const FString Key = TEXT("key");
const FString ContentType = TEXT("contenttype");
const FString Source = TEXT("source");
}
UENUM(BlueprintType)
enum class EContentSourceType : uint8
{
Image,
PDF,
TextFile,
InlineText
};
USTRUCT(BlueprintType)
struct FContentSource
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly)
FString Key;
UPROPERTY(BlueprintReadOnly)
EContentSourceType ContentType = EContentSourceType::InlineText;
UPROPERTY(BlueprintReadOnly)
FString Source;
};
UCLASS(BlueprintType)
class TABLE_API UContentWidgetData : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadOnly)
FString WidgetType;
UPROPERTY(BlueprintReadOnly)
TArray<FContentSource> DataSources;
UPROPERTY(BlueprintReadOnly)
TArray<FString> SubWidgetNames;
UPROPERTY(BlueprintReadOnly)
TArray<UContentWidgetData*> SubWidgets;
};
+36
View File
@@ -0,0 +1,36 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using System.IO;
using UnrealBuildTool;
public class Table : ModuleRules
{
public Table(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core",
"CoreUObject",
"Engine",
"InputCore",
"EnhancedInput",
"Sockets",
"Networking",
"UMG" ,
"Json",
"JsonUtilities",
"ImageWrapper"
});
PrivateDependencyModuleNames.AddRange(new string[] { });
// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Table.h"
#include "Modules/ModuleManager.h"
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, Table, "Table" );
+6
View File
@@ -0,0 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
+15
View File
@@ -0,0 +1,15 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;
public class TableEditorTarget : TargetRules
{
public TableEditorTarget( TargetInfo Target) : base(Target)
{
Type = TargetType.Editor;
DefaultBuildSettings = BuildSettingsVersion.V6;
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7;
ExtraModuleNames.Add("Table");
}
}