first commit
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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" );
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user