增加多屏显示,视频播放

This commit is contained in:
liuyunhui
2025-09-19 14:40:23 +08:00
parent fded7b9a51
commit 9076a5ea41
112 changed files with 24986 additions and 21 deletions
@@ -0,0 +1,68 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
using System.IO;
using UnrealBuildTool;
public class NeoKinectUnreal : ModuleRules
{
public NeoKinectUnreal(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PrivateIncludePaths.AddRange(new[]
{
"NeoKinectUnreal/Private"
// ... add other private include paths required here ...
});
PublicDependencyModuleNames.AddRange(new[]
{
"Core",
"CoreUObject",
// ... add other public dependencies that you statically link with here ...
});
PrivateDependencyModuleNames.AddRange(new[]
{
"Engine",
"RenderCore",
"RHI",
// ... add private dependencies that you statically link with here ...
});
string thirdPartyPath = Path.GetFullPath(Path.Combine(ModuleDirectory, "../ThirdParty/"));
string neoKinectPath = Path.Combine(thirdPartyPath, "NeoKinect");
string kinectPath = Path.Combine(thirdPartyPath, "Kinect");
PublicSystemLibraryPaths.Add(Path.Combine(neoKinectPath, "Lib"));
PublicIncludePaths.Add(Path.Combine(neoKinectPath, "Inc"));
PublicSystemLibraryPaths.Add(Path.Combine(kinectPath, "Lib"));
PublicIncludePaths.Add(Path.Combine(kinectPath, "Inc"));
const string FaceLibraryName = "Kinect20.Face";
// Add static libraries
PublicSystemLibraries.AddRange(new[]
{
"NeoKinect.lib",
"Kinect20.lib",
FaceLibraryName + ".lib"
});
// Add dynamic libraries
const string KinectFaceDllName = FaceLibraryName + ".dll";
PublicDelayLoadDLLs.AddRange(new[]
{
KinectFaceDllName
});
// Define library name for code to be able to check for its presence
PrivateDefinitions.Add("KINECT_FACE_DLL_NAME=\"" + KinectFaceDllName + "\"");
// Stage face library files when packaging
const string ProjectDLLDir = "$(ProjectDir)/Binaries/Win64";
RuntimeDependencies.Add(Path.Combine(ProjectDLLDir, KinectFaceDllName));
RuntimeDependencies.Add(Path.Combine(ProjectDLLDir, "NuiDatabase/..."), StagedFileType.NonUFS);
}
}
@@ -0,0 +1,329 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#include "KinectThread.h"
#include "HAL/RunnableThread.h"
/*-----------------------------------------------------------------------------
ThreadFrameBuffer
-----------------------------------------------------------------------------*/
using namespace NeoKinect;
FThreadFrameBuffer::~FThreadFrameBuffer()
{
DeallocateBuffers();
}
void FThreadFrameBuffer::Configure(
KinectFrameType Frame,
KinectPixelFormat Format,
KinectCoordinateSpace Space,
bool IsRemapped)
{
this->FrameType = Frame;
this->PixelFormat = Format;
this->TargetSpace = Space;
this->bRemapped = IsRemapped;
}
bool FThreadFrameBuffer::AllocateBuffers(uint32 Size)
{
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*FString::Printf(TEXT("Kinect_AllocateBuffers_%d"), FrameType));
if (IsAllocated())
{
return true;
}
Buffer = new uint8[Size];
BackBuffer = new uint8[Size];
return Buffer && BackBuffer;
}
void FThreadFrameBuffer::DeallocateBuffers()
{
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*FString::Printf(TEXT("Kinect_DeallocateBuffers_%d"), FrameType));
bIsNew = false;
if (Buffer)
{
Lock();
delete[] Buffer;
Buffer = nullptr;
Unlock();
}
if (BackBuffer)
{
LockWrite();
delete[] BackBuffer;
BackBuffer = nullptr;
UnlockWrite();
}
}
bool FThreadFrameBuffer::IsAllocated() const
{
return Buffer && BackBuffer;
}
const uint8* FThreadFrameBuffer::GetReadBuffer(bool bConsume /*= true*/)
{
if (bConsume)
{
if (Buffer && bIsNew)
{
bIsNew = false;
return Buffer;
}
return nullptr;
}
return Buffer;
}
uint8* FThreadFrameBuffer::GetWriteBuffer() const
{
return BackBuffer;
}
void FThreadFrameBuffer::SetDirty()
{
SwapBuffers();
bIsNew = true;
}
void FThreadFrameBuffer::SwapBuffers()
{
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*FString::Printf(TEXT("Kinect_SwapBuffers_%d"), FrameType));
Lock();
uint8* Temp = Buffer;
Buffer = BackBuffer;
BackBuffer = Temp;
Unlock();
}
bool FThreadFrameBuffer::TryLock()
{
return ReadMutex.TryLock();
}
void FThreadFrameBuffer::Lock()
{
ReadMutex.Lock();
}
void FThreadFrameBuffer::Unlock()
{
ReadMutex.Unlock();
}
void FThreadFrameBuffer::LockWrite()
{
WriteMutex.Lock();
}
void FThreadFrameBuffer::UnlockWrite()
{
WriteMutex.Unlock();
}
/*-----------------------------------------------------------------------------
KinectThread
-----------------------------------------------------------------------------*/
FKinectThread* FKinectThread::Runnable = nullptr;
FCriticalSection FKinectThread::BodiesFacesCritSec;
FThreadFrameBuffer FKinectThread::FrameBuffers[static_cast<uint64>(EKinectFrame::Count)];
bool FKinectThread::bUseBodyTracking = false;
bool FKinectThread::bUseFaceTracking = false;
FKinectThread::FKinectThread(KinectSensor *pSensor) :
pKinect(pSensor),
bBodiesDirty(false),
bFacesDirty(false)
{
Thread = FRunnableThread::Create(this, TEXT("FKinectThread")); // Windows give 8MB memory by default
if (!Thread)
UE_LOG(KinectThreadLog, Error, TEXT("Failed to create Kinect thread!"));
}
FKinectThread::~FKinectThread()
{
if (Thread) delete Thread;
Thread = nullptr;
}
FKinectThread* FKinectThread::InitKinectPolling(KinectSensor *pSensor)
{
// Create new instance of thread if it does not exist and the platform
// supports multi threading.
if (!Runnable && FPlatformProcess::SupportsMultithreading())
{
Runnable = new FKinectThread(pSensor);
}
return Runnable;
}
void FKinectThread::EnsureCompletion()
{
TRACE_CPUPROFILER_EVENT_SCOPE(Kinect_EnsureThreadCompletion)
Stop();
if (Thread)
Thread->WaitForCompletion();
}
void FKinectThread::Shutdown()
{
TRACE_CPUPROFILER_EVENT_SCOPE(Kinect_ShutdownThread)
if (Runnable)
{
Runnable->EnsureCompletion();
delete Runnable;
Runnable = nullptr;
}
}
const KinectBody* FKinectThread::GetBodiesData()
{
if (bBodiesDirty)
{
bBodiesDirty = false;
return Bodies;
}
return nullptr;
}
const KinectFace* FKinectThread::GetFacesData()
{
if (bFacesDirty)
{
bFacesDirty = false;
return Faces;
}
return nullptr;
}
bool FKinectThread::Init()
{
UE_LOG(KinectThreadLog, Log, TEXT("Kinect thread initialized."));
return true;
}
uint32 FKinectThread::Run()
{
constexpr float UpdateInterval = 1.f / cDesiredFPS;
UE_LOG(KinectThreadLog, Log, TEXT("Kinect thread running."));
// while not told to stop this thread
while (StopTaskCounter.GetValue() == 0)
{
// try locking any frames necessary
constexpr int FrameTypesCount = static_cast<int>(KinectFrameType::Count);
TRACE_CPUPROFILER_EVENT_SCOPE(KinectThread_Run)
{
TRACE_CPUPROFILER_EVENT_SCOPE(Kinect_LockFrames)
for (int i = 0; i < FrameTypesCount; ++i)
{
const KinectFrameType FrameType = static_cast<KinectFrameType>(i);
if (pKinect->GetIsUsingFrame(FrameType))
{
pKinect->LockLatestFrame(FrameType);
}
}
}
if (bUseBodyTracking)
{
TRACE_CPUPROFILER_EVENT_SCOPE(Kinect_GetBodyData)
// process bodies before face tracking - face tracking depends on bodies data
BodiesFacesCritSec.Lock();
if (pKinect->GetLatestFrameData(Bodies))
{
bBodiesDirty = true;
}
BodiesFacesCritSec.Unlock();
}
if (bUseFaceTracking)
{
TRACE_CPUPROFILER_EVENT_SCOPE(Kinect_GetFaceData)
// process face tracking
BodiesFacesCritSec.Lock();
if (pKinect->GetLatestFrameData(Faces))
{
bFacesDirty = true;
}
BodiesFacesCritSec.Unlock();
}
// update any necessary buffers
for (FThreadFrameBuffer& FrameBuffer : FrameBuffers)
{
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*FString::Printf(TEXT("Kinect_UpdateBuffer_%d"), FrameBuffer.FrameType));
if (FrameBuffer.IsAllocated() && pKinect->GetIsFrameLocked(FrameBuffer.FrameType))
{
FrameBuffer.LockWrite();
// If this is a remapped type, use the correct function.
// If the update works, marks buffer as new
if (FrameBuffer.bRemapped)
{
if (pKinect->GetRemappedFrameData(
FrameBuffer.FrameType, FrameBuffer.TargetSpace,
FrameBuffer.GetWriteBuffer(), FrameBuffer.PixelFormat))
{
FrameBuffer.SetDirty();
}
}
else
{
uint8* Buffer = FrameBuffer.GetWriteBuffer();
if (pKinect->GetLatestFrameData(
FrameBuffer.FrameType, Buffer,
FrameBuffer.PixelFormat))
{
FrameBuffer.SetDirty();
}
}
FrameBuffer.UnlockWrite();
}
}
{
TRACE_CPUPROFILER_EVENT_SCOPE(Kinect_UnlockFrames)
// unlock frames
for (int i = 0; i < FrameTypesCount; ++i)
{
pKinect->UnlockLatestFrame(static_cast<KinectFrameType>(i));
}
}
// give time for Kinect to update its frames.
FPlatformProcess::Sleep(UpdateInterval);
}
// ended successfully!
return 0;
}
void FKinectThread::Stop()
{
StopTaskCounter.Increment();
}
void FKinectThread::Exit()
{
UE_LOG(KinectThreadLog, Log, TEXT("Kinect thread ended."));
}
@@ -0,0 +1,547 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#include "NeoKinectBody.h"
#include "Helpers/GuardedNeoKinect.h"
#include "NeoKinectManager.h"
UNeoKinectBody::UNeoKinectBody()
: BodyIndex(0),
TrackingId(0),
bIsTracked(false),
bIsRestricted(false),
HandLeftState(EKinectHandState::NotTracked),
HandLeftTrackingState(EKinectTrackingState::NotTracked),
HandRightState(EKinectHandState::NotTracked),
HandRightTrackingState(EKinectTrackingState::NotTracked),
LeanTrackingState(EKinectTrackingState::NotTracked),
Lean(FVector2D(0.f, 0.f))
{
using namespace NeoKinect;
Joints.SetNum(KinectJointCount);
for (int32 JointIdx = 0; JointIdx < KinectJointCount; ++JointIdx)
{
Joints[JointIdx].Type = static_cast<EKinectJointType>(JointIdx);
}
}
UNeoKinectBody::~UNeoKinectBody()
{
Joints.Empty();
}
void UNeoKinectBody::GetHandStateAsExec(EKinectBodySide Hand, EKinectHandState& States)
{
if (Hand == EKinectBodySide::Left)
{
States = HandLeftState;
}
else
{
States = HandRightState;
}
}
EKinectHandState UNeoKinectBody::GetHandState(EKinectBodySide Hand) const
{
if (Hand == EKinectBodySide::Left)
return HandLeftState;
else
return HandRightState;
}
void UNeoKinectBody::GetHandConfidenceAsExec(EKinectBodySide Hand, EKinectTrackingState& States)
{
if (Hand == EKinectBodySide::Left)
{
States = HandLeftTrackingState;
}
else
{
States = HandRightTrackingState;
}
}
EKinectTrackingState UNeoKinectBody::GetHandConfidence(EKinectBodySide Hand) const
{
if (Hand == EKinectBodySide::Left)
return HandLeftTrackingState;
else
return HandRightTrackingState;
}
void UNeoKinectBody::GetLeanConfidenceAsExec(EKinectTrackingState& States)
{
States = LeanTrackingState;
}
TArray<FKinectJoint> UNeoKinectBody::GetJoints()
{
return Joints;
}
FVector UNeoKinectBody::GetJointLocation(EKinectJointType Joint)
{
return Joints[static_cast<int32>(Joint)].Location;
}
FRotator UNeoKinectBody::GetJointOrientation(EKinectJointType Joint)
{
return Joints[static_cast<int32>(Joint)].Orientation;
}
FVector UNeoKinectBody::GetJointLocationColor(EKinectJointType Joint)
{
return Joints[static_cast<int32>(Joint)].ColorLocation;
}
FRotator UNeoKinectBody::GetJointOrientationColor(EKinectJointType Joint)
{
return Joints[static_cast<int32>(Joint)].ColorOrientation;
}
void UNeoKinectBody::GetJointConfidenceAsExec(EKinectJointType Joint, EKinectTrackingState& States)
{
States = Joints[static_cast<int32>(Joint)].Confidence;
}
EKinectTrackingState UNeoKinectBody::GetJointConfidence(EKinectJointType Joint)
{
return Joints[static_cast<int32>(Joint)].Confidence;
}
float UNeoKinectBody::GetJointsDistance(EKinectJointType JointA, EKinectJointType JointB)
{
return FVector::Dist(Joints[static_cast<int32>(JointB)].Location, Joints[static_cast<int32>(JointA)].Location);
}
float UNeoKinectBody::GetBoneLength(EKinectJointType Joint)
{
const FVector p1 = Joints[static_cast<int32>(Joint)].Location;
switch (Joint)
{
case EKinectJointType::SpineBase:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::SpineMid)].Location);
case EKinectJointType::SpineMid:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::SpineBase)].Location);
case EKinectJointType::Neck:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::SpineShoulder)].Location);
case EKinectJointType::Head:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::Neck)].Location);
case EKinectJointType::ShoulderLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::SpineShoulder)].Location);
case EKinectJointType::ElbowLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::ShoulderLeft)].Location);
case EKinectJointType::WristLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::ElbowLeft)].Location);
case EKinectJointType::HandLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::WristLeft)].Location);
case EKinectJointType::ShoulderRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::SpineShoulder)].Location);
case EKinectJointType::ElbowRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::ShoulderRight)].Location);
case EKinectJointType::WristRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::ElbowRight)].Location);
case EKinectJointType::HandRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::WristRight)].Location);
case EKinectJointType::HipLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::SpineBase)].Location);
case EKinectJointType::KneeLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::HipLeft)].Location);
case EKinectJointType::AnkleLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::KneeLeft)].Location);
case EKinectJointType::FootLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::AnkleLeft)].Location);
case EKinectJointType::HipRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::SpineBase)].Location);
case EKinectJointType::KneeRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::HipRight)].Location);
case EKinectJointType::AnkleRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::KneeRight)].Location);
case EKinectJointType::FootRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::AnkleRight)].Location);
case EKinectJointType::SpineShoulder:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::SpineMid)].Location);
case EKinectJointType::HandTipLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::WristLeft)].Location);
case EKinectJointType::ThumbLeft:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::HandLeft)].Location);
case EKinectJointType::HandTipRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::WristRight)].Location);
case EKinectJointType::ThumbRight:
return FVector::Dist(p1, Joints[static_cast<int32>(EKinectJointType::HandRight)].Location);
default:
return 0.f;
}
}
float UNeoKinectBody::JointDotVector(EKinectJointType Joint, FVector Vector)
{
Vector.Normalize();
return FVector::DotProduct(
Vector,
FRotationMatrix(Joints[static_cast<int32>(Joint)].Orientation).GetScaledAxis(EAxis::Z));
}
void UNeoKinectBody::UpdateFromKinectBody(const NeoKinect::KinectBody& kBody, const NeoKinect::KinectSensor& Sensor)
{
using namespace NeoKinect;
// For throwing BeginTrack/Lost events after update
bool bWasTracked = bIsTracked;
ClippedEdges.Left = kBody.clippedEdges.left;
ClippedEdges.Right = kBody.clippedEdges.right;
ClippedEdges.Top = kBody.clippedEdges.top;
ClippedEdges.Bottom = kBody.clippedEdges.bottom;
bIsTracked = kBody.isTracked;
bIsRestricted = kBody.isRestricted;
TrackingId = kBody.trackingId;
HandLeftState = static_cast<EKinectHandState>(kBody.handLeftState);
HandLeftTrackingState = static_cast<EKinectTrackingState>(kBody.handLeftTrackingState);
HandRightState = static_cast<EKinectHandState>(kBody.handRightState);
HandRightTrackingState = static_cast<EKinectTrackingState>(kBody.handRightTrackingState);
LeanTrackingState = static_cast<EKinectTrackingState>(kBody.leanTrackingState);
Lean.Set(kBody.lean.x, kBody.lean.y);
// update joints only if being tracked
if (bIsTracked)
{
// camera space joints transforms
for (int32 j = 0; j < KinectJointCount; ++j)
{
FKinectJoint& uJoint = Joints[j];
KinectJoint joint = kBody.joints[j];
uJoint.Confidence = static_cast<EKinectTrackingState>(joint.trackingState);
uJoint.Location.Set(
joint.position.Z * 100.f,
-joint.position.X * 100.f,
joint.position.Y * 100.f);
// Quaternion conversion from Kinect camera's coord system to Unreal's
FQuat Quat = FQuat(
-joint.orientation.z,
joint.orientation.x,
-joint.orientation.y,
joint.orientation.w
);
// In order to match the joints orientations to Unreal Mannequin, we
// still have some problems:
// - Kinect's bones extend along its UP axis, which is Z in Unreal.
// But Unreal's Mannequin skeleton's bones extend along X (as it is
// in Maya). So we re-create the orientation swapping the axes.
//
// - Some bones extend towards X, some against it. That's good for doing
// mirrored rotations in the animation software, so we'll keep it here
// by flipping some axes.
//
// - The pelvis and spine bones front axis is the same as the limbs side axis.
// In the Mannequin these match up, so we'll have to invert some here.
#define JNT_ID(Name) static_cast<uint8>(EKinectJointType::Name)
uint8 JointIndex = static_cast<uint8>(uJoint.Type);
if (JointIndex <= JNT_ID(Head) || JointIndex == JNT_ID(SpineShoulder))
{ // for spine bones
uJoint.Orientation = FRotationMatrix::MakeFromXZ(
Quat.GetAxisZ(),
Quat.GetAxisY()).Rotator();
}
else if (JointIndex <= JNT_ID(WristLeft)
|| JointIndex >= JNT_ID(HipRight) && JointIndex <= JNT_ID(FootRight))
{ // for left arm and right leg
uJoint.Orientation = FRotationMatrix::MakeFromXZ(
Quat.GetAxisZ(),
Quat.GetAxisX()).Rotator();
}
else if ( (JointIndex >= JNT_ID(ShoulderRight) && JointIndex <= JNT_ID(WristRight))
|| (JointIndex >= JNT_ID(HipLeft) && JointIndex <= JNT_ID(FootLeft))
|| JointIndex == JNT_ID(HandTipRight) || JointIndex == JNT_ID(ThumbRight)
)
{ // for right arm and left leg
uJoint.Orientation = FRotationMatrix::MakeFromXZ(
-Quat.GetAxisZ(),
-Quat.GetAxisX()).Rotator();
}
else if (JointIndex == JNT_ID(HandTipLeft) || JointIndex == JNT_ID(ThumbLeft))
{ // Left hand
uJoint.Orientation = FRotationMatrix::MakeFromXZ(
Quat.GetAxisZ(),
Quat.GetAxisX()).Rotator();
}
else if (JointIndex == JNT_ID(HandLeft))
{ // left hand
uJoint.Orientation = FRotationMatrix::MakeFromXZ(
Quat.GetAxisZ(),
-Quat.GetAxisX()).Rotator();
}
else if (JointIndex == JNT_ID(HandRight))
{ // right hand
uJoint.Orientation = FRotationMatrix::MakeFromXZ(
-Quat.GetAxisZ(),
Quat.GetAxisX()).Rotator();
}
#undef JNT_ID
} // camera space joints transforms
if (UNeoKinectManager::IsUsingJointsColorSpaceTransforms())
{
// color space with depth joints transforms
for (int32 j = 0; j < KinectJointCount; ++j)
{
FKinectJoint& uJoint = Joints[j];
KinectJoint joint = kBody.joints[j];
// Convert camera space location to color space with depth
Vector3 ColorDepthLocation = UNeoKinectManager::CameraLocationToColorWithDepth(Vector3(joint.position));
if (ColorDepthLocation.IsZero())
{
// fallback for when the joint can not be seen in color camera
if (uJoint.Type == EKinectJointType::SpineBase) // oh shit! Our reference is gone!
{
uJoint.ColorLocation = uJoint.Location;
continue;
}
FKinectJoint &SpineBase = Joints[static_cast<int32>(EKinectJointType::SpineBase)];
uJoint.ColorLocation = SpineBase.ColorLocation + uJoint.Location - SpineBase.Location;
continue;
}
uJoint.ColorLocation.Set(
ColorDepthLocation.z * 100.0f,
-ColorDepthLocation.x * 100.0f,
ColorDepthLocation.y * 100.0f);
} // color space with depth joints transforms
// orientations in color space with depth
#define JOINT(Name) Joints[static_cast<int32>(EKinectJointType::Name)]
#define J_DIR(FromJoint, ToJoint) JOINT(ToJoint).ColorLocation - JOINT(FromJoint).ColorLocation
#define J_ROT(JointName, Twist) JOINT(JointName).ColorOrientation = FRotationMatrix::MakeFromX##Twist(XDir, Twist##Dir).Rotator()
FVector XDir, YDir, ZDir, Dir1, Dir2;
FQuat Rot1, Rot2, RLerp;
float JointsDot;
// Spine Base
XDir = J_DIR(SpineBase, SpineMid);
ZDir = J_DIR(HipLeft, HipRight);
J_ROT(SpineBase, Z);
// Spine Middle
// same XDir
ZDir = (ZDir + J_DIR(ShoulderLeft, ShoulderRight)) / 2.0f; // intermediary between hip and shoulders twist
J_ROT(SpineMid, Z);
// Spine Shoulder
XDir = J_DIR(SpineMid, SpineShoulder);
ZDir = J_DIR(ShoulderLeft, ShoulderRight);
J_ROT(SpineShoulder, Z);
// Neck
XDir = J_DIR(SpineShoulder, Neck);
// same ZDir
J_ROT(Neck, Z);
// Head
XDir = J_DIR(Neck, Head);
// same ZDir
J_ROT(Head, Z);
// shoulder left
XDir = J_DIR(SpineShoulder, ShoulderLeft);
ZDir = J_DIR(SpineMid, SpineShoulder);
J_ROT(ShoulderLeft, Z);
// elbow left
XDir = J_DIR(ShoulderLeft, ElbowLeft);
// interpolate between a rot for fully stretched arm and one for bent
Dir1 = FVector::CrossProduct(J_DIR(ElbowLeft, WristLeft), J_DIR(ElbowLeft, ShoulderLeft)); // bent
Rot1 = FRotationMatrix::MakeFromXZ(XDir, Dir1).ToQuat(); // bent
Dir2 = FRotationMatrix(JOINT(ShoulderLeft).ColorOrientation).GetScaledAxis(EAxis::Y); // stretched
Rot2 = FRotationMatrix::MakeFromXY(XDir, Dir2).ToQuat(); // stretched
Dir1 = J_DIR(ShoulderLeft, ElbowLeft); Dir2 = J_DIR(ElbowLeft, WristLeft);
Dir1.Normalize(); Dir2.Normalize();
JointsDot = FVector::DotProduct(Dir1, Dir2);
RLerp = FQuat::Slerp(Rot2, Rot1,
FMath::Clamp(FMath::Acos(JointsDot) / 0.349f /*~20°*/, 0.f, 1.f)
);
RLerp.Normalize();
JOINT(ElbowLeft).ColorOrientation = RLerp.Rotator();
// wrist left
XDir = J_DIR(ElbowLeft, WristLeft);
YDir = J_DIR(HandLeft, ThumbLeft);
J_ROT(WristLeft, Y);
// hand left
XDir = J_DIR(WristLeft, HandTipLeft);
ZDir = J_DIR(ThumbLeft, HandLeft);
J_ROT(HandLeft, Z);
// hand tip Left
JOINT(HandTipLeft).ColorOrientation = JOINT(HandLeft).ColorOrientation;
// thumb left
XDir = J_DIR(HandLeft, ThumbLeft);
YDir = FRotationMatrix(JOINT(HandLeft).ColorOrientation).GetScaledAxis(EAxis::Y);
J_ROT(ThumbLeft, Y);
// shoulder right
XDir = J_DIR(ShoulderRight, SpineShoulder);
ZDir = J_DIR(SpineShoulder, SpineMid);
J_ROT(ShoulderRight, Z);
// elbow right
XDir = J_DIR(ElbowRight, ShoulderRight);
// interpolate between a rot for fully stretched arm and one for bent
Dir1 = FVector::CrossProduct(J_DIR(ElbowRight, WristRight), J_DIR(ElbowRight, ShoulderRight)); // bent
Rot1 = FRotationMatrix::MakeFromXZ(XDir, Dir1).ToQuat(); // bent
Dir2 = FRotationMatrix(JOINT(ShoulderRight).ColorOrientation).GetScaledAxis(EAxis::Y); // stretched
Rot2 = FRotationMatrix::MakeFromXY(XDir, Dir2).ToQuat(); // stretched
Dir1 = J_DIR(ShoulderRight, ElbowRight); Dir2 = J_DIR(ElbowRight, WristRight);
Dir1.Normalize(); Dir2.Normalize();
JointsDot = FVector::DotProduct(Dir1, Dir2);
RLerp = FQuat::Slerp(Rot2, Rot1,
FMath::Clamp(FMath::Acos(JointsDot) / 0.349f /*~20°*/, 0.f, 1.f)
);
RLerp.Normalize();
JOINT(ElbowRight).ColorOrientation = RLerp.Rotator();
// wrist right
XDir = J_DIR(WristRight, ElbowRight);
YDir = J_DIR(ThumbRight, HandRight);
J_ROT(WristRight, Y);
// hand right
XDir = J_DIR(HandTipRight, WristRight);
ZDir = -YDir;
J_ROT(HandRight, Z);
// hand tip right
JOINT(HandTipRight).ColorOrientation = JOINT(HandRight).ColorOrientation;
// thumb right
XDir = J_DIR(ThumbRight, HandRight);
YDir = FRotationMatrix(JOINT(HandRight).ColorOrientation).GetScaledAxis(EAxis::Y);
J_ROT(ThumbRight, Y);
// Hip Left
XDir = J_DIR(HipLeft, SpineBase);
ZDir = J_DIR(SpineMid, SpineBase);
J_ROT(HipLeft, Z);
// Knee left
XDir = J_DIR(KneeLeft, HipLeft);
Dir1 = FVector::CrossProduct(J_DIR(KneeLeft, AnkleLeft), J_DIR(KneeLeft, HipLeft)); // bent
Rot1 = FRotationMatrix::MakeFromXZ(XDir, Dir1).ToQuat(); // bent
Dir2 = FRotationMatrix(JOINT(SpineMid).ColorOrientation).GetScaledAxis(EAxis::Y); // stretched
Rot2 = FRotationMatrix::MakeFromXY(XDir, Dir2).ToQuat(); // stretched
Dir1 = J_DIR(HipLeft, KneeLeft); Dir2 = J_DIR(KneeLeft, AnkleLeft);
Dir1.Normalize(); Dir2.Normalize();
JointsDot = FVector::DotProduct(Dir1, Dir2);
RLerp = FQuat::Slerp(Rot2, Rot1, FMath::Clamp((FMath::Acos(JointsDot) - 0.2618f) / 0.5236f, 0.f, 1.f));
RLerp.Normalize();
JOINT(KneeLeft).ColorOrientation = RLerp.Rotator();
// ankle left
XDir = J_DIR(AnkleLeft, KneeLeft);
ZDir = FRotationMatrix(JOINT(KneeLeft).ColorOrientation).GetScaledAxis(EAxis::Z);
J_ROT(AnkleLeft, Z);
// foot left
// same X and Z dirs
J_ROT(FootLeft, Z);
// hip right
XDir = J_DIR(SpineBase, HipRight);
ZDir = J_DIR(SpineBase, SpineMid);
J_ROT(HipRight, Z);
// Knee Right
XDir = J_DIR(HipRight, KneeRight);
Dir1 = FVector::CrossProduct(J_DIR(KneeRight, AnkleRight), J_DIR(KneeRight, HipRight)); // bent
Rot1 = FRotationMatrix::MakeFromXZ(XDir, Dir1).ToQuat(); // bent
Dir2 = -FRotationMatrix(JOINT(SpineMid).ColorOrientation).GetScaledAxis(EAxis::Y); // stretched
Rot2 = FRotationMatrix::MakeFromXY(XDir, Dir2).ToQuat(); // stretched
Dir1 = J_DIR(HipRight, KneeRight); Dir2 = J_DIR(KneeRight, AnkleRight);
Dir1.Normalize(); Dir2.Normalize();
JointsDot = FVector::DotProduct(Dir1, Dir2);
RLerp = FQuat::Slerp(Rot2, Rot1, FMath::Clamp((FMath::Acos(JointsDot) - 0.2618f) / 0.5236f, 0.f, 1.f));
RLerp.Normalize();
JOINT(KneeRight).ColorOrientation = RLerp.Rotator();
// Ankle right
XDir = J_DIR(KneeRight, AnkleRight);
ZDir = FRotationMatrix(JOINT(KneeRight).ColorOrientation).GetScaledAxis(EAxis::Z);
J_ROT(AnkleRight, Z);
// foot right
// same X and Z dirs
J_ROT(FootRight, Z);
#undef JOINT
#undef J_DIR
#undef J_ROT
} // if (bUsingJointsColorSpaceTransforms)
} // if (bIsTracked)
if (bWasTracked != bIsTracked)
{
if (bIsTracked)
OnBodyBeginTrack.Broadcast(BodyIndex);
else
OnBodyLost.Broadcast(BodyIndex);
}
}
void UNeoKinectBody::Reset()
{
using namespace NeoKinect;
ClippedEdges.Left =
ClippedEdges.Right =
ClippedEdges.Top =
ClippedEdges.Bottom = false;
bIsTracked = false;
bIsRestricted = false;
TrackingId = 0Ui64;
HandLeftState = EKinectHandState::NotTracked;
HandLeftTrackingState = EKinectTrackingState::NotTracked;
HandRightState = EKinectHandState::NotTracked;
HandRightTrackingState = EKinectTrackingState::NotTracked;
LeanTrackingState = EKinectTrackingState::NotTracked;
Lean.Set(0.f, 0.f);
if (Joints.Num() != KinectJointCount)
return;
for (int32 j = 0; j < KinectJointCount; ++j)
{
if (!Joints.IsValidIndex(j))
continue;
FKinectJoint& Joint = Joints[j];
Joint.Confidence = EKinectTrackingState::NotTracked;
Joint.Location.Set(0.f, 0.f, 0.f);
Joint.ColorLocation.Set(0.f, 0.f, 0.f);
Joint.Orientation.Roll =
Joint.Orientation.Pitch =
Joint.Orientation.Yaw = 0.f;
Joint.ColorOrientation.Roll =
Joint.ColorOrientation.Pitch =
Joint.ColorOrientation.Yaw = 0.f;
}
}
@@ -0,0 +1,216 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#include "NeoKinectFace.h"
#include "NeoKinectBody.h"
#include "NeoKinectManager.h"
void FIntBoundingBox::FromRectI(const RectI& Rect)
{
Left = Rect.Left;
Top = Rect.Top;
Right = Rect.Right;
Bottom = Rect.Bottom;
}
void FFacePoints2D::FromKinectFacePoints(const NeoKinect::KinectFacePoints& Points)
{
LeftEye.X = Points.leftEye.x;
LeftEye.Y = Points.leftEye.y;
RightEye.X = Points.rightEye.x;
RightEye.Y = Points.rightEye.y;
Nose.X = Points.nose.x;
Nose.Y = Points.nose.y;
LeftMouthCorner.X = Points.leftMouthCorner.x;
LeftMouthCorner.Y = Points.leftMouthCorner.y;
RightMouthCorner.X = Points.rightMouthCorner.x;
RightMouthCorner.Y = Points.rightMouthCorner.y;
}
void FFacePoints3D::FromKinectFacePoints(const NeoKinect::KinectFacePoints& ColorPoints)
{
NeoKinect::Vector3 Location = UNeoKinectManager::ColorWithDepthFromColorOnly(ColorPoints.leftEye) * 100.f;
LeftEye.Set(-Location.z, Location.x, Location.y);
Location = UNeoKinectManager::ColorWithDepthFromColorOnly(ColorPoints.rightEye) * 100.f;
RightEye.Set(-Location.z, Location.x, Location.y);
Location = UNeoKinectManager::ColorWithDepthFromColorOnly(ColorPoints.nose) * 100.f;
Nose.Set(-Location.z, Location.x, Location.y);
Location = UNeoKinectManager::ColorWithDepthFromColorOnly(ColorPoints.leftMouthCorner) * 100.f;
LeftMouthCorner.Set(-Location.z, Location.x, Location.y);
Location = UNeoKinectManager::ColorWithDepthFromColorOnly(ColorPoints.rightMouthCorner) * 100.f;
RightMouthCorner.Set(-Location.z, Location.x, Location.y);
}
UNeoKinectFace::UNeoKinectFace()
: Index(INDEX_NONE)
, bIsTracked(false)
, TrackingId(INDEX_NONE)
, IsEngaged(EDetectionResult::Unknown)
, IsHappy(EDetectionResult::Unknown)
, IsLookingAway(EDetectionResult::Unknown)
, IsMouthMoved(EDetectionResult::Unknown)
, IsMouthOpen(EDetectionResult::Unknown)
, IsLeftEyeClosed(EDetectionResult::Unknown)
, IsRightEyeClosed(EDetectionResult::Unknown)
, IsWearingGlasses(EDetectionResult::Unknown)
{}
UNeoKinectFace::~UNeoKinectFace()
{}
void UNeoKinectFace::GetEyeClosedAsExec(EKinectBodySide Eye, EDetectionResult& Result)
{
if (Eye == EKinectBodySide::Left)
{
Result = IsLeftEyeClosed;
}
else
{
Result = IsRightEyeClosed;
}
}
EDetectionResult UNeoKinectFace::GetEyeClosed(EKinectBodySide Eye) const
{
if (Eye == EKinectBodySide::Left)
return IsLeftEyeClosed;
return IsRightEyeClosed;
}
void UNeoKinectFace::GetIsEngagedAsExec(EDetectionResult& Result)
{
Result = IsEngaged;
}
void UNeoKinectFace::GetIsHappyAsExec(EDetectionResult& Result)
{
Result = IsHappy;
}
void UNeoKinectFace::GetIsLookingAwayAsExec(EDetectionResult& Result)
{
Result = IsLookingAway;
}
void UNeoKinectFace::GetIsMouthMovedAsExec(EDetectionResult& Result)
{
Result = IsMouthMoved;
}
void UNeoKinectFace::GetIsMouthOpenAsExec(EDetectionResult& Result)
{
Result = IsMouthOpen;
}
void UNeoKinectFace::GetIsWearingGlassesAsExec(EDetectionResult& Result)
{
Result = IsWearingGlasses;
}
bool UNeoKinectFace::IsFromBody(const UNeoKinectBody* Body) const
{
return Body->TrackingId == TrackingId;
}
void UNeoKinectFace::UpdateFromKinectFace(const NeoKinect::KinectFace& kFace)
{
const bool bWasTracked = bIsTracked;
bIsTracked = kFace.bIsTracked;
TrackingId = kFace.trackingId;
ColorBoundingBox.FromRectI(kFace.boxColor);
InfraredBoundingBox.FromRectI(kFace.boxInfrared);
ColorAlignmentPoints.FromKinectFacePoints(kFace.pointsColor);
InfraredAlignmentPoints.FromKinectFacePoints(kFace.pointsInfrared);
ColorWithDepthAlignmentPoints.FromKinectFacePoints(kFace.pointsColor);
/* Camera space face transform */
Location.Set(
kFace.pivot.Z * 100.f,
-kFace.pivot.X * 100.f,
kFace.pivot.Y * 100.f);
const FQuat Quat = FQuat(
-kFace.rotation.z,
kFace.rotation.x,
-kFace.rotation.y,
kFace.rotation.w
);
Orientation = FRotationMatrix::MakeFromXZ(
Quat.GetAxisZ(),
-Quat.GetAxisY()).Rotator();
/* Color space face location */
if (UNeoKinectManager::IsUsingJointsColorSpaceTransforms())
{
using namespace NeoKinect;
// Convert camera space location to color space with depth
const Vector3 ColorDepthLocation = UNeoKinectManager::CameraLocationToColorWithDepth(Vector3(kFace.pivot));
if (ColorDepthLocation.IsZero())
{
ColorLocation = Location;
}
else
{
ColorLocation.Set(
ColorDepthLocation.z * 100.0f,
-ColorDepthLocation.x * 100.0f,
ColorDepthLocation.y * 100.0f);
}
}
IsEngaged = static_cast<EDetectionResult>(kFace.isEngaged);
IsHappy = static_cast<EDetectionResult>(kFace.isHappy);
IsLookingAway = static_cast<EDetectionResult>(kFace.isLookingAway);
IsMouthMoved = static_cast<EDetectionResult>(kFace.isMouthMoved);
IsMouthOpen = static_cast<EDetectionResult>(kFace.isMouthOpen);
IsLeftEyeClosed = static_cast<EDetectionResult>(kFace.isLeftEyeClosed);
IsRightEyeClosed = static_cast<EDetectionResult>(kFace.isRightEyeClosed);
IsWearingGlasses = static_cast<EDetectionResult>(kFace.isWearingGlasses);
if (bWasTracked != bIsTracked)
{
if (bIsTracked)
OnFaceBeginTrack.Broadcast(Index);
else
OnFaceLost.Broadcast(Index);
}
}
void UNeoKinectFace::Reset()
{
bIsTracked = false;
TrackingId = 0ULL;
ColorBoundingBox = FIntBoundingBox();
InfraredBoundingBox = FIntBoundingBox();
ColorAlignmentPoints = FFacePoints2D();
InfraredAlignmentPoints = FFacePoints2D();
Location.Set(0.f, 0.f, 0.f);
ColorLocation = Location;
Orientation.Yaw =
Orientation.Pitch =
Orientation.Roll = 0.f;
IsEngaged =
IsHappy =
IsLookingAway =
IsMouthMoved =
IsMouthOpen =
IsLeftEyeClosed =
IsRightEyeClosed =
IsWearingGlasses = EDetectionResult::Unknown;
}
@@ -0,0 +1,5 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#include "Modules/ModuleManager.h"
IMPLEMENT_MODULE(FDefaultModuleImpl, NeoKinectUnreal)
@@ -0,0 +1,10 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#pragma once
#include "Windows/AllowWindowsPlatformTypes.h"
#pragma warning(push)
#pragma warning(disable : 4471)
#include "NeoKinect.h"
#pragma warning(pop)
#include "Windows/HideWindowsPlatformTypes.h"
@@ -0,0 +1,127 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#pragma once
#include "Helpers/GuardedNeoKinect.h"
#include "NeoKinectEnums.h"
#include <atomic>
#include "CoreMinimal.h"
#include "HAL/Runnable.h"
DECLARE_LOG_CATEGORY_CLASS(KinectThreadLog, Log, All);
struct FThreadFrameBuffer
{
NeoKinect::KinectFrameType FrameType = NeoKinect::KinectFrameType::Count;
NeoKinect::KinectPixelFormat PixelFormat = NeoKinect::KinectPixelFormat::None;
NeoKinect::KinectCoordinateSpace TargetSpace = NeoKinect::KinectCoordinateSpace::Count;
bool bRemapped = false;
~FThreadFrameBuffer();
void Configure(
NeoKinect::KinectFrameType Frame,
NeoKinect::KinectPixelFormat PixelFormat,
NeoKinect::KinectCoordinateSpace Space,
bool IsRemapped);
bool AllocateBuffers(uint32 Size);
void DeallocateBuffers();
bool IsAllocated() const;
// Returns an updated buffer. If it was already consumed, return nullptr
const uint8* GetReadBuffer(bool bConsume = true);
uint8* GetWriteBuffer() const;
void SetDirty();
bool TryLock();
void Lock();
void Unlock();
void LockWrite();
void UnlockWrite();
private:
std::atomic<bool> bIsNew = false;
uint8 *Buffer = nullptr;
uint8 *BackBuffer = nullptr;
FCriticalSection ReadMutex;
FCriticalSection WriteMutex;
void SwapBuffers();
};
// Kinect polling thread
class FKinectThread : public FRunnable
{
private:
/*
Kinect updates its frames, at most, at 30fps.
But, as some of them will be available at slightly different times, I'll poll
it at double that rate to be sure.
*/
static constexpr float cDesiredFPS = 60.f;
// Singleton instance, can access the thread any time via static accessor, if it is active
static FKinectThread *Runnable;
// Thread to run FRunnable on
FRunnableThread *Thread;
// Stop this thread? Uses Thread Safe Counter
FThreadSafeCounter StopTaskCounter;
// Kinect sensor
NeoKinect::KinectSensor *pKinect;
// Bodies data
NeoKinect::KinectBody Bodies[NeoKinect::KinectBodyCount];
// Face tracking data
NeoKinect::KinectFace Faces[NeoKinect::KinectBodyCount];
public:
// Constructor
FKinectThread(NeoKinect::KinectSensor *pSensor);
// Destructor
~FKinectThread() override;
// Frames data. These control which frame buffers should or not be updated.
static FThreadFrameBuffer FrameBuffers[static_cast<uint64>(EKinectFrame::Count)];
// Controls whether body data should be read
static bool bUseBodyTracking;
// True if body data was updated since last reading
bool bBodiesDirty;
// Controls whether face tracking data should be read
static bool bUseFaceTracking;
// True if faces data was updated since last reading
bool bFacesDirty;
// Mutex for body/face data thread safety
static FCriticalSection BodiesFacesCritSec;
/*
Start the thread and the worker from static (easy access)
This code ensures only 1 KinectThread will be able to run at a time.
This function returns a handle to the newly started instance.
*/
static FKinectThread *InitKinectPolling(NeoKinect::KinectSensor *pSensor);
// Makes sure this thread has stopped properly
void EnsureCompletion();
// Shuts down the thread. Static so it can easily be called from outside the thread context
static void Shutdown();
// Gets last polled bodies data. Returns nullptr if no new data
const NeoKinect::KinectBody *GetBodiesData();
// Gets last polled faces data. Returns nullptr if no new data
const NeoKinect::KinectFace *GetFacesData();
// Begin FRunnable interface
virtual bool Init() override;
virtual uint32 Run() override;
virtual void Stop() override;
virtual void Exit() override;
// End FRunnable interface
};
@@ -0,0 +1,297 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#pragma once
#include "CoreMinimal.h"
#include "NeoKinectEnums.h"
#include "NeoKinectBody.generated.h"
namespace NeoKinect
{
struct KinectBody;
class KinectSensor;
}
/**
* Joint properties.
*
* The Location and Orientation of joints, by default, is given in camera space,
* which center is a point in the Kinect sensor not matching the color camera.
* So, to use joints over the color frame image, these coordinates must be reprojected.
* To get these reprojected values, use ColorLocation and ColorOrientation.
*/
USTRUCT(BlueprintType, Category = "NeoKinect|Structs")
struct FKinectJoint
{
GENERATED_BODY()
/** Joint type. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Joints")
EKinectJointType Type;
/** Joint tracking confidence. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Joints")
EKinectTrackingState Confidence;
/** Joint location relative to Kinect sensor in camera space. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "position translation transform camera"))
FVector Location;
/** Joint orientation relative to Kinect sensor in camera space. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "transform rotation angles camera"))
FRotator Orientation;
/** Joint location relative to Kinect sensor in color space with depth. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "position translation transform color"))
FVector ColorLocation;
/** Joint orientation relative to Kinect sensor in color space. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "transform rotation angles color"))
FRotator ColorOrientation;
FKinectJoint() :
Type(EKinectJointType::AnkleLeft),
Confidence(EKinectTrackingState::NotTracked),
Location(FVector::ZeroVector),
Orientation(FRotator::ZeroRotator),
ColorLocation(FVector::ZeroVector),
ColorOrientation(FRotator::ZeroRotator)
{}
};
/**
* Which edges are clipping a body on the view frustum.
*/
USTRUCT(BlueprintType, Category = "NeoKinect|Structs")
struct FKinectClippedEdges
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body")
bool Left;
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body")
bool Right;
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body")
bool Top;
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body")
bool Bottom;
FKinectClippedEdges() :
Left(false), Right(false), Top(false), Bottom(false)
{}
};
/**
* Represents a trackable body.
*/
UCLASS(BlueprintType, Category = "NeoKinect|Body")
class NEOKINECTUNREAL_API UNeoKinectBody : public UObject
{
GENERATED_BODY()
public:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FBodyTrackDelegate, int32, BodyIndex);
UNeoKinectBody();
~UNeoKinectBody() override;
/** Retrieves this body index (0~5). */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body")
int32 BodyIndex;
/** Body's unique Tracking ID. */
UPROPERTY()
int64 TrackingId;
/** Body clipped edges from the sensor's view. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body", Meta = (Keywords = "cut borders"))
FKinectClippedEdges ClippedEdges;
/** If this body represents a tracked user. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body")
bool bIsTracked;
/** If body's movement is restricted somehow. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body")
bool bIsRestricted;
/** Left hand pose */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Hands")
EKinectHandState HandLeftState;
/** Left hand tracking state */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Hands")
EKinectTrackingState HandLeftTrackingState;
/** Right hand pose */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Hands")
EKinectHandState HandRightState;
/** Right hand tracking state */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body|Hands")
EKinectTrackingState HandRightTrackingState;
/** Body lean tracking confidence. */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body")
EKinectTrackingState LeanTrackingState;
/**
* Body lean.
* Ranges from -1 to 1 being left to right in X and back to front in Y.
* Values of -1 and 1 represent roughly 45 degrees of inclination.
*/
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Body", Meta = (Keywords = "inclination"))
FVector2D Lean;
UPROPERTY()
TArray<FKinectJoint> Joints;
/** Called when this Body object starts tracking an user */
UPROPERTY(BlueprintAssignable, Category = "NeoKinect|Body")
FBodyTrackDelegate OnBodyBeginTrack;
/** Called when this Body object loses track of its user */
UPROPERTY(BlueprintAssignable, Category = "NeoKinect|Body")
FBodyTrackDelegate OnBodyLost;
/**
* Get Left/Right hand state as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param Hand Which hand?
* @param States Current Hand state.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Body|Hands", Meta = (ExpandEnumAsExecs = "States"))
void GetHandStateAsExec(EKinectBodySide Hand, EKinectHandState& States);
/**
* Get Left/Right hand state.
*
* @param Hand Which hand?
* @return Current Hand state.
*/
UFUNCTION(BlueprintPure, Category = "SamrtKinect|Body|Hands")
EKinectHandState GetHandState(EKinectBodySide Hand) const;
/**
* Get Left/Right hand tracking state as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param Hand Which hand?
* @param States Current Hand tracking state.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Body|Hands", Meta = (ExpandEnumAsExecs = "States"))
void GetHandConfidenceAsExec(EKinectBodySide Hand, EKinectTrackingState& States);
/**
* Get Left/Right hand tracking state.
*
* @param Hand Which hand?
* @return Current Hand tracking state.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Hands")
EKinectTrackingState GetHandConfidence(EKinectBodySide Hand) const;
/**
* Get body lean tracking confidence as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param States Lean tracking state.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Body", Meta = (ExpandEnumAsExecs = "States", Keywords = "inclination"))
void GetLeanConfidenceAsExec(EKinectTrackingState& States);
/**
* Contains this body's each joint tracking state and transform.
*
* @return This body's each joint tracking state and transform.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints")
TArray<FKinectJoint> GetJoints();
/**
* Get a Joint's location relative to Kinect sensor.
*
* @param Joint The joint type.
* @return Joint's location relative to Kinect sensor.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "position translation transform"))
FVector GetJointLocation(EKinectJointType Joint);
/**
* Get a Joint's orientation relative to Kinect sensor.
*
* @param Joint The joint type.
* @return Joint's orientation relative to Kinect sensor.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "rotation angles transform"))
FRotator GetJointOrientation(EKinectJointType Joint);
/**
* Get a Joint's location relative to Kinect sensor aligned with the Color frame.
* So, if a camera is created with the same FOV as the Kinect Color camera's and
* the Color frame texture is used as background, this location will align perfectly
* with the corresponding joint in the Color image.
*
* @param Joint The joint type.
* @return Joint's location relative to Kinect sensor.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "position translation transform"))
FVector GetJointLocationColor(EKinectJointType Joint);
/**
* Get a Joint's orientation relative to Kinect sensor aligned with the Color
* aligned joint location.
*
* @param Joint The joint type.
* @return Joint's orientation relative to Kinect sensor.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "rotation angles transform"))
FRotator GetJointOrientationColor(EKinectJointType Joint);
/**
* Get a Joint's tracking confidence as exec pins.
* Useful for redirecting the code path, like the Branch node.
*
* @param Joint The joint type.
* @param States Joint's tracking confidence.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Body|Joints", Meta = (ExpandEnumAsExecs = "States"))
void GetJointConfidenceAsExec(EKinectJointType Joint, EKinectTrackingState& States);
/**
* Get a Joint's tracking confidence.
*
* @param Joint The joint type.
* @return Joint's tracking confidence.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints")
EKinectTrackingState GetJointConfidence(EKinectJointType Joint);
/**
* Calculate the distance between two joints in the Kinect sensor space.
*
* @param JointA First joint.
* @param JointB Second joint.
* @return Distance between the two joints.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "size length"))
float GetJointsDistance(EKinectJointType JointA, EKinectJointType JointB);
/**
* Calculate the distance from a Joint's location to it's parent's location.
* Useful for getting bone sizes. Eg.: for Left Elbow, the result will be the
* distance from Left Elbow to Left Shoulder. Spine Base is the skeleton's root,
* so it will return 0.
*
* @param Joint The joint type.
* @return Distance from Joint to it's parent.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "size length"))
float GetBoneLength(EKinectJointType Joint);
/**
* The Dot product between a Joint direction and a Vector.
*
* @return -1 ~ 1: 1 = same direction as vector. -1 = opposite from vector.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints", Meta = (Keywords = "pointing direction lookat"))
float JointDotVector(EKinectJointType Joint, FVector Vector);
void UpdateFromKinectBody(const NeoKinect::KinectBody& kBody, const NeoKinect::KinectSensor& Sensor);
void Reset();
};
@@ -0,0 +1,134 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#pragma once
#include "CoreMinimal.h"
#include "NeoKinectEnums.generated.h"
/**
* The types of frames that can be read from the Kinect sensor.
*/
UENUM(BlueprintType, Category = "NeoKinect|Enums")
enum class EKinectFrame : uint8
{
/** Normal camera texture. Standard RGBA format (alpha = 1) */
Color = 0,
/** Depth sensor texture. Red channel only. Values are in millimeters */
Depth = 1,
/** Depth sensor ranging from 0 to 1. Standard RGBA format (alpha = 1) */
NormalizedDepth = 2,
/** Body index value in Red channel. In material editor, multiply Red by 255
* to get actual index value (0~5) */
BodyIndex = 3,
/** Body indexes with a color for each index. Standard RGBA format.
* Alpha = 1 where there's a body, 0 otherwise */
BodyIndexColor = 4 UMETA(DisplayName = "Colored Body Index"),
/** Not implemented */
Color_DepthSpace = 5 UMETA(DisplayName = "Color in Depth Space"),
/** Depth sensor image remapped to fit the color frame. Red channel only.
* Values in millimeters, 0 where remapping is not possible. */
Depth_ColorSpace = 6 UMETA(DisplayName = "Depth in Color Space"),
/** Body index values remapped to fit the bodies in the color frame.
* Red channel only. In material editor, multiply Red by 255 to get actual
* index value (0~5). */
BodyIndex_ColorSpace= 7 UMETA(DisplayName = "Body Index in Color Space"),
/** Infrared sensor image. Red channel only, usually very dark. */
Infrared = 8,
/** Long exposure infrared sensor image. Red channel only. Can lower the
* sensor's FPS to get brighter image */
LongExposureInfrared= 9,
/** Cast to int to get the number of frame types */
Count = 10
};
/**
* Body sides.
*/
UENUM(BlueprintType, Category = "NeoKinect|Enums")
enum class EKinectBodySide : uint8
{
Left = 0,
Right = 1
};
/**
* Possible hand states
*/
UENUM(BlueprintType, Category = "NeoKinect|Enums")
enum class EKinectHandState : uint8
{
/** Pretty close to NotTracked. */
Unknown = 0,
NotTracked = 1,
Open = 2,
Closed = 3,
/** Closed hand with index and middle fingers pointing up, near each other */
Lasso = 4
};
/**
* Tracking confidence.
*/
UENUM(BlueprintType, Category = "NeoKinect|Enums")
enum class EKinectTrackingState : uint8
{
NotTracked = 0,
/** Tracked by deduction. Sometimes good, many times terrible. */
Inferred = 1,
Tracked = 2
};
/**
* The joints that the Kinect sensor tracks on each body. 25 total.
*/
UENUM(BlueprintType, Category = "NeoKinect|Enums")
enum class EKinectJointType : uint8
{
/** Near the pelvis */
SpineBase = 0,
/** Near the stomach */
SpineMid = 1 UMETA(DisplayName = "Spine Middle"),
Neck = 2,
Head = 3,
ShoulderLeft = 4 UMETA(DisplayName = "Left Shoulder"),
ElbowLeft = 5 UMETA(DisplayName = "Left Elbow"),
WristLeft = 6 UMETA(DisplayName = "Left Wrist"),
/** In the palm center */
HandLeft = 7 UMETA(DisplayName = "Left Hand"),
ShoulderRight = 8 UMETA(DisplayName = "Right Shoulder"),
ElbowRight = 9 UMETA(DisplayName = "Right Elbow"),
WristRight = 10 UMETA(DisplayName = "Right Wrist"),
/** In the palm center */
HandRight = 11 UMETA(DisplayName = "Right Hand"),
/** Femur/Hip joint */
HipLeft = 12 UMETA(DisplayName = "Left Hip"),
KneeLeft = 13 UMETA(DisplayName = "Left Knee"),
AnkleLeft = 14 UMETA(DisplayName = "Left Ankle"),
/** Tip of the foot */
FootLeft = 15 UMETA(DisplayName = "Left Foot"),
/** Femur/Hip joint */
HipRight = 16 UMETA(DisplayName = "Right Hip"),
KneeRight = 17 UMETA(DisplayName = "Right Knee"),
AnkleRight = 18 UMETA(DisplayName = "Right Ankle"),
/** Tip of the foot */
FootRight = 19 UMETA(DisplayName = "Right Foot"),
/** Between the collar bones */
SpineShoulder = 20,
HandTipLeft = 21 UMETA(DisplayName = "Left Hand Tip"),
ThumbLeft = 22 UMETA(DisplayName = "Left Thumb"),
HandTipRight = 23 UMETA(DisplayName = "Right Hand Tip"),
ThumbRight = 24 UMETA(DisplayName = "Right Thumb")
};
/**
* Face properties detection results
*/
UENUM(BlueprintType, Category = "NeoKinect|Enums")
enum class EDetectionResult : uint8
{
Unknown = 0,
No = 1,
/** Consider it more as a "No" */
Maybe = 2,
Yes = 3
};
@@ -0,0 +1,290 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#pragma once
#include "NeoKinectEnums.h"
#include "Windows/AllowWindowsPlatformTypes.h"
#pragma warning(push)
#pragma warning(disable : 4471)
#include "Kinect.Face.h"
#pragma warning(pop)
#include "Windows/HideWindowsPlatformTypes.h"
#include "NeoKinectFace.generated.h"
class UNeoKinectBody;
namespace NeoKinect
{
struct KinectFace;
struct KinectFacePoints;
}
/**
* Describes the edges of a rectangle in a texture (int values)
*/
USTRUCT(BlueprintType, Category = "NeoKinect|Structs")
struct FIntBoundingBox
{
GENERATED_BODY()
// Left rectangle edge
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
int32 Left = 0;
// Top rectangle edge
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
int32 Top = 0;
// Right rectangle edge
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
int32 Right = 0;
// Bottom rectangle edge
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
int32 Bottom = 0;
void FromRectI(const RectI& Rect);
};
/**
* Lists the 2D of each face alignment point.
*/
USTRUCT(BlueprintType, Category = "NeoKinect|Structs")
struct FFacePoints2D
{
GENERATED_BODY()
// Left eye location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector2D LeftEye = FVector2D::Zero();
// Right eye location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector2D RightEye = FVector2D::Zero();
// Nose location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector2D Nose = FVector2D::Zero();
// Left mouth corner location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector2D LeftMouthCorner = FVector2D::Zero();
// Right mouth corner location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector2D RightMouthCorner = FVector2D::Zero();
void FromKinectFacePoints(const NeoKinect::KinectFacePoints& Points);
};
/**
* Lists the 3D locations of each face alignment point.
*/
USTRUCT(BlueprintType, Category = "NeoKinect|Structs")
struct FFacePoints3D
{
GENERATED_BODY()
// Left eye location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector LeftEye = FVector::Zero();
// Right eye location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector RightEye = FVector::Zero();
// Nose location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector Nose = FVector::Zero();
// Left mouth corner location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector LeftMouthCorner = FVector::Zero();
// Right mouth corner location
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector RightMouthCorner = FVector::Zero();
void FromKinectFacePoints(const NeoKinect::KinectFacePoints& ColorPoints);
};
/**
* Represents a trackable body's face.
*/
UCLASS(BlueprintType, Category = "NeoKinect|Face")
class NEOKINECTUNREAL_API UNeoKinectFace : public UObject
{
GENERATED_BODY()
public:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FFaceTrackDelegate, int32, FaceIndex);
UNeoKinectFace();
~UNeoKinectFace() override;
// This face's index in the faces array
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
int32 Index;
// If this face is being tracked.
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
bool bIsTracked;
// Face tracking Id. Matches it's body TrackingId.
UPROPERTY()
int64 TrackingId;
// The face bounding box in Color space.
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FIntBoundingBox ColorBoundingBox;
// The face bounding box in Depth space.
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FIntBoundingBox InfraredBoundingBox;
// The face alignment points in Color space.
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FFacePoints2D ColorAlignmentPoints;
// The face alignment points in Depth space.
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FFacePoints2D InfraredAlignmentPoints;
/**
* The face alignment points aligned with the Color frame from the camera's
* point of view, at a distance taken from Depth frame.
* For this to have valid values the remapped Depth in Color Space frame must be activated.
*/
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FFacePoints3D ColorWithDepthAlignmentPoints;
// The face rotation.
UPROPERTY(BlueprintReadOnly, Meta = (Keywords = "rotation quaternion angles transform"), Category = "NeoKinect|Face")
FRotator Orientation;
// The head pivot location.
UPROPERTY(BlueprintReadOnly, Meta = (Keywords = "location position center head translation transform"), Category = "NeoKinect|Face")
FVector Location;
/** Head location aligned with the Color frame and with depth.
* Only valid if color transforms were activated using SetUseJointsColorSpaceTransforms */
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
FVector ColorLocation;
// Is the user engaged?
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
EDetectionResult IsEngaged;
// Does the user looks happy?
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
EDetectionResult IsHappy;
// Is the user looking away?
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
EDetectionResult IsLookingAway;
// Did the user mouth move?
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
EDetectionResult IsMouthMoved;
// Is the user mouth opened?
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
EDetectionResult IsMouthOpen;
// Is the user left eye closed?
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
EDetectionResult IsLeftEyeClosed;
// Is the user right eye closed?
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
EDetectionResult IsRightEyeClosed;
// Is the user wearing glasses?
UPROPERTY(BlueprintReadOnly, Category = "NeoKinect|Face")
EDetectionResult IsWearingGlasses;
/** Called when this face object starts tracking a user's face */
UPROPERTY(BlueprintAssignable, Category = "NeoKinect|Face")
FFaceTrackDelegate OnFaceBeginTrack;
/** Called when this face object loses its user's face */
UPROPERTY(BlueprintAssignable, Category = "NeoKinect|Face")
FFaceTrackDelegate OnFaceLost;
/**
* Checks if left/right eye is closed and use the result as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param Eye Which eye?
* @param Result Is selected eye closed? Consider 'Maybe' more as a 'No'.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Face", Meta = (ExpandEnumAsExecs = "Result"))
void GetEyeClosedAsExec(EKinectBodySide Eye, EDetectionResult& Result);
/**
* Checks if left/right eye is closed.
*
* @param Eye Which eye?
* @return Is selected eye closed? Consider 'Maybe' more as a 'No'.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Face")
EDetectionResult GetEyeClosed(EKinectBodySide Eye) const;
/**
* Get IsEngaged as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param Result Consider 'Maybe' more as a 'No'.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Face", Meta = (ExpandEnumAsExecs = "Result"))
void GetIsEngagedAsExec(EDetectionResult& Result);
/**
* Gets IsHappy as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param Result Consider 'Maybe' more as a 'No'.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Face", Meta = (ExpandEnumAsExecs = "Result"))
void GetIsHappyAsExec(EDetectionResult& Result);
/**
* Gets IsLookingAway as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param Result Consider 'Maybe' more as a 'No'.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Face", Meta = (ExpandEnumAsExecs = "Result"))
void GetIsLookingAwayAsExec(EDetectionResult& Result);
/**
* Gets IsMouthMoved as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param Result Consider 'Maybe' more as a 'No'.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Face", Meta = (ExpandEnumAsExecs = "Result"))
void GetIsMouthMovedAsExec(EDetectionResult& Result);
/**
* Gets IsMouthOpen as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param Result Consider 'Maybe' more as a 'No'.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Face", Meta = (ExpandEnumAsExecs = "Result"))
void GetIsMouthOpenAsExec(EDetectionResult& Result);
/**
* Gets IsWearingGlasses as execution pins.
* Useful for redirecting code path, like the Branch node.
*
* @param Result Consider 'Maybe' more as a 'No'.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Face", Meta = (ExpandEnumAsExecs = "Result"))
void GetIsWearingGlassesAsExec(EDetectionResult& Result);
/**
* Checks if this face belongs to the passed Body instance.
*
* @param Body The body to test from.
* @return True if this face belongs to the passed body.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Face")
bool IsFromBody(const UNeoKinectBody* Body) const;
void UpdateFromKinectFace(const NeoKinect::KinectFace& kFace);
void Reset();
};
@@ -0,0 +1,612 @@
// Copyright 2015-2023 Rodrigo Villani Pereira
#pragma once
#include "Helpers/GuardedNeoKinect.h"
#include "NeoKinectEnums.h"
#include "PixelFormat.h"
#include "Containers/Ticker.h"
#include "NeoKinectManager.generated.h"
class FKinectThread;
class UNeoKinectBody;
class UNeoKinectFace;
class UTextureRenderTarget2D;
DECLARE_LOG_CATEGORY_CLASS(NeoKinectLog, Log, All);
/** Structure to manage Kinect frame types lifetime and updating */
struct FFrameTextureData
{
UTextureRenderTarget2D *Texture = nullptr;
uint32 Width = 0;
uint32 Height = 0;
uint32 PixelSize = 0;
uint32 BufferSize = 0;
EPixelFormat UPixelFormat = EPixelFormat::PF_Unknown;
NeoKinect::KinectPixelFormat KPixelFormat = NeoKinect::KinectPixelFormat::None;
EKinectFrame UFrameType = EKinectFrame::Count;
NeoKinect::KinectFrameType KFrameType = NeoKinect::KinectFrameType::Count;
NeoKinect::KinectCoordinateSpace TargetSpace = NeoKinect::KinectCoordinateSpace::Count;
bool bIsSRGB = false;
bool bRemapped = false;
~FFrameTextureData();
inline bool IsTextureValid() const;
void Release();
NeoKinect::FrameDescription GetFrameDescription(const NeoKinect::KinectSensor *Kinect) const;
};
/**
* Access the Kinect v2 sensor capabilities.
*
* When using any Remapped type of frame or any Coordinate Remapping methods,
* Depth frame use will automatically be switched on, since those functionalities
* depend on it. But don't worry about performance. The depth frame will only
* get written and updated to a texture on the GPU if the user explicitly
* asks for it using SetUseFrame().
*
* When using Face Tracking, Body Tracking will also be automatically switched on.
* Face Tracking depends on Body Tracking for the head pivot position and for
* fixing issues when looking for a Face.
*/
UCLASS(Category = "NeoKinect", BlueprintType)
class NEOKINECTUNREAL_API UNeoKinectManager : public UObject
{
GENERATED_BODY()
public:
/** Stores the bodies that Kinect can track. The array is created only once
* and lasts until the end of the program. */
static TArray<UNeoKinectBody*> Bodies;
/** Stores the faces that Kinect can track. The array is created only once
* and lasts until the end of the program. */
static TArray<UNeoKinectFace*> Faces;
/** Stores all the possible frame types Kinect or the program can create.
* The array is created only once and lasts until the end of the program. */
static FFrameTextureData FramesData[static_cast<int32>(EKinectFrame::Count)];
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FBodyTrackDelegate, UNeoKinectBody*, Body);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FFaceTrackDelegate, UNeoKinectFace*, Face);
/** Constructor: used only to bind the static dispatcher to the bodies/faces
* tracking events. */
UNeoKinectManager();
/** Destructor: unbind the static dispatcher events and makes sure to
* un-initialize the Kinect sensor before the program exits. */
~UNeoKinectManager() override;
/*-------------------------------------------------------------------------
EVENTS
-------------------------------------------------------------------------*/
private:
/** Class singleton used to give Blueprints access to the tracking events. */
static UNeoKinectManager* EventsDispatcher;
public:
/** Called when the Kinect sensor has just detected a new user's body. */
UPROPERTY(BlueprintAssignable, Category = "NeoKinect")
FBodyTrackDelegate OnBodyBeginTrack;
/** Called when the Kinect sensor has just lost track of a user's body. */
UPROPERTY(BlueprintAssignable, Category = "NeoKinect")
FBodyTrackDelegate OnBodyLost;
/** Called when the Kinect sensor has just detected a new user's face. */
UPROPERTY(BlueprintAssignable, Category = "NeoKinect")
FFaceTrackDelegate OnFaceBeginTrack;
/** Called when the Kinect sensor has just lost track of a user's face. */
UPROPERTY(BlueprintAssignable, Category = "NeoKinect")
FFaceTrackDelegate OnFaceLost;
/**
* Use the object result of this function to bind to the Kinect body/face
* tracking events. You can also bind to individual bodies/faces using their
* respective objects.
*
* @return Kinect events dispatcher object.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect")
static UNeoKinectManager* GetKinectEventsDispatcher();
/*////////////////////////////////////////////////////////////////////////////
BLUEPRINT FUNCTIONS
////////////////////////////////////////////////////////////////////////////*/
/*
* BASICS
*/
/**
* Initializes the Kinect sensor.
*
* Even though this function always works without errors, check if the sensor
* has really started by calling IsKinectAvailable. It can take a while, so
* check in intervals until it does.
*/
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Init Kinect Sensor", Keywords = "turn on initialize enable start begin"), Category = "NeoKinect")
static void InitSensor();
/**
* Returns if sensor is already on.
*
* It may take a while for the sensor to initialize, so keep checking. If it
* takes too long, either there's no connected sensor or it's malfunctioning.
*
* @return true if Sensor is available and operational.
*/
UFUNCTION(BlueprintPure, meta = (Keywords = "on working works usable"), Category = "NeoKinect")
static bool IsKinectAvailable();
/**
* Use very carefully! Will shutdown the sensor and also invalidate any of its
* frame textures (color, depth etc). Make sure you call this when none of them
* are in use (preferably when quitting the game). Remember: with great power,
* comes great responsibility!
*/
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Uninit Kinect Sensor", Keywords = "turn off uninitialize disable end"), Category = "NeoKinect")
static void UninitSensor();
/**
* Retrieves the Kinect sensor or NULL if not initialized.
*
* @return The Kinect sensor or NULL if not initialized.
*/
static NeoKinect::KinectSensor* GetSensor();
/**
* Gets sensor tilt in degrees. It represents the pitch of the sensor in Unreal
* coordinates (X pointing the same direction as the Kinect camera). Negative
* values mean it's pointing down.
*
* @return Kinect sensor tilt in degrees. Negative values mean it's pointing down.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect", meta = (DisplayName = "Get Kinect Tilt"))
static float GetSensorTilt();
/**
* Kinect height from the ground and ground normal. Only works if the sensor
* can see the ground.
* Bodies reading must be in use, as this data is read from the Body frames.
*
* @param Height Kinect distance from the ground
* @return Ground normal
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect", meta = (DisplayName = "Get Kinect Ground Plane", Keywords = "height normal location floor"))
static FVector GetGroundPlane(float &Height);
/*
* EXPOSURE CONTROL
*/
/*
* Fully automatic exposure setting with exposure compensation. Only affects the Color frame.
* @param exposureCompensation Negative value gives an underexposed image,
* positive gives an overexposed image.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|ColorExposure", meta = (DisplayName = "Set Kinect to Auto Exposure", Keywords = "image frame color brightness"))
static void SetAutoExposure(float exposureCompensation = 0.0f);
/*
* Sets a pseudo-exposure time in ms. Only affects the Color frame.
*
* The actual frame integration time is set to a multiple of fluorescent light period
* that is shorter than the requested time e.g. requesting 16 ms will set 10 ms
* in Australia (100Hz), 8.33 ms in USA (120Hz).
* The gain is automatically set to compensate for the reduced integration time.
*
* Requesting less than a single fluorescent light period will set the integration time
* to the requested value.
*
* To set the shortest non-flickering integration period for any country, simply set
* a pseudo-exposure time of between (10.0, 16.667) ms, which will automatically drop
* the integration time to 10 or 8.3 ms depending on country, while setting the analog
* gain control to a brighter value.
*
* @param pseudoExposureTime Pseudo-exposure time in ms. [0.0, 640]ms
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|ColorExposure", meta = (DisplayName = "Set Kinect to Semi Auto Exposure", Keywords = "image frame color brightness"))
static void SetSemiAutoExposure(float pseudoExposureTime);
/*
* Manually set frame exposure time in ms and analog gain. Only affects the Color frame.
* @param frameExposureTime Time of exposure for each frame, in ms. [0.0, 640]ms
* @param analogGain Exposure analog gain.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|ColorExposure", meta = (DisplayName = "Set Kinect to Manual Exposure", Keywords = "image frame color brightness"))
static void SetManualExposure(float frameExposureTime, float analogGain);
/*
* COORDINATE REMAPPING
*/
/**
* Converts a 3D location in Camera space to Color space with depth, so the new
* location matches the Color frame position from camera's point of view
* without turning into 2D.
*
* @param CameraLocation The 3D Camera space coordinate to be converted (in Unreal units)
* @return Realigned 3D coordinate that matches the Color frame from the camera's
* point of view (in Unreal units)
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Remapping", Meta = (Keywords = "convert coordinate space remap"))
static FVector CameraLocationToColorWithDepth(const FVector& CameraLocation);
/** Native NeoKinect implementation of the above */
static NeoKinect::Vector3 CameraLocationToColorWithDepth(NeoKinect::Vector3 CameraLocation);
/**
* Remaps a 3D camera location to a 2D point in the Color frame.
*
* @param CameraLocation The 3D Camera space coordinate to be remapped (in Unreal units)
* @return The point location in the Color frame that matches the Camera
* location input (in pixels)
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Remapping", Meta = (Keywords = "convert coordinate space remap"))
static FVector2D CameraLocationToColorPoint(const FVector& CameraLocation);
/**
* Remaps a 3D camera location to a point in the Depth frame.
*
* @param CameraLocation The 3D Camera space coordinate to be remapped (in Unreal units)
* @return The point location in the Depth frame that matches the Camera
* location input (in pixels)
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Remapping", Meta = (Keywords = "convert coordinate space remap"))
static FVector2D CameraLocationToDepthPoint(const FVector& CameraLocation);
/**
* Creates a Color with depth point from a Color point only. Viewed from the
* camera perspective, this point aligns with the Color frame without losing
* it's depth.
*
* This method uses the remapped Color buffer in Depth space so, for it to
* work, the Depth in Color Space frame must be in use.
*
* @param ColorLocation A point in the Color frame to be remapped (in pixels)
* @return The 3D version of the Color point (in Unreal units)
*/
static NeoKinect::Vector3 ColorWithDepthFromColorOnly(const NeoKinect::Vector2& ColorLocation);
/**
* Remaps a point location from the Depth frame to the Color frame.
*
* @param DepthPoint A point in the Depth frame (in pixels)
* @return Remapped Depth frame point to a Color frame point (in pixels)
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Remapping", Meta = (Keywords = "convert coordinate space remap"))
static FVector2D DepthPointToColorPoint(FVector2D DepthPoint);
/**
* Remaps a point location from the Depth frame to the 3D camera space.
*
* @param DepthPoint A point in the Depth frame (in pixels)
* @return Remapped Depth frame point to a 3D Camera location (in Unreal units)
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Remapping", Meta = (Keywords = "convert coordinate space remap"))
static FVector DepthPointToCameraLocation(FVector2D DepthPoint);
/**
* Remaps a point from the Color frame to the Depth frame.
*
* @param ColorPoint A point in the Color frame (in pixels)
* @return Color point remapped to its related location in the Depth frame (in pixels)
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Remapping", Meta = (Keywords = "convert coordinate space remap"))
static FVector2D ColorPointToDepthPoint(FVector2D ColorPoint);
/*
* TEXTURES (Color, Depth etc)
*/
/**
* Get a frame horizontal and vertical field of view.
*
* @param FrameType The frame type to request the FOV from.
* @return x = horizontal FOV, y = vertical FOV
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Frames", Meta = (Keywords = "width height FOV texture angle field of view", DisplayName = "Get Kinect FOV"))
static FVector2D GetFrameFieldOfView(EKinectFrame FrameType);
/**
* Get a frame texture width and height in pixels.
*
* @param FrameType The frame type to request the size from.
* @return x = width, y = height
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Frames", Meta = (Keywords = "width height texture resolution", DisplayName = "Get Kinect Frame Size"))
static FIntPoint GetFrameSize(EKinectFrame FrameType);
/**
* Enables or disables updating of a Kinect frame type.
*
* @param FrameType The frame type to enable/disable
* @param bEnable If the selected frame type should be enabled
* @return The Frame texture. Null if Enable = false.
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Frames", Meta = (Keywords = "texture sensor camera enable disable toggle", DisplayName = "Set Use Kinect Frame"))
static UTextureRenderTarget2D* SetUseFrame(EKinectFrame FrameType, bool bEnable);
/**
* Returns if a Kinect frame type is enabled. If it is, it means you can get
* it's texture with GetFrame.
*
* @param FrameType The frame type to ask for.
* @return If FrameType is enabled or not.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Frames", Meta = (Keywords = "texture sensor camera enabled", DisplayName = "Get Is Using Kinect Frame"))
static bool GetIsUsingFrame(EKinectFrame FrameType);
/**
* Get the texture that represents the requested frame type.
*
* @param FrameType The frame type to get the texture from.
* @return The texture that represents the requested frame type. Null if it has
* not been enabled.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Frames", Meta = (Keywords = "texture sensor camera", DisplayName = "Get Kinect Frame"))
static UTextureRenderTarget2D* GetFrame(EKinectFrame FrameType);
/**
* Gets the depth value from a specific pixel on the Depth frame.
*
* @param Coordinate A point in the Depth frame (in pixels)
* @return The Depth value (distance from the Kinect sensor) in the input point
* (in centimeters)
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Frames", Meta = (Keywords = "sample texture sensor camera"))
static float GetDepthFramePixel(FVector2D Coordinate);
/**
* Gets the depth value from a specific pixel on the Color frame
*
* @param Coordinate A point in the Color frame (in pixels)
* @return The Depth value (distance from the Kinect sensor) in the input point
* (in centimeters)
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Frames", Meta = (Keywords = "sample texture remap"))
static float GetDepthFromColorFramePixel(FVector2D Coordinate);
/**
* Gets the color value from a specific pixel on the Color frame.
*
* @param Coordinate A point in the Color frame (in pixels)
* @return The Color value in the input point
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Frames", Meta = (Keywords = "sample texture sensor camera"))
static FLinearColor GetColorFramePixel(FVector2D Coordinate);
/*
* BODIES / JOINTS
*/
/**
* Array of all bodies (tracked and not tracked).
* The bodies in the array are updated automatically, so you don't need to
* request the array more than once.
*
* @return Bodies array.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body", meta = (DisplayName = "Get Kinect Bodies"))
static TArray<UNeoKinectBody*> GetBodies();
/**
* Array with only the currently tracked bodies.
*
* @return The currently tracked bodies.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body", meta = (DisplayName = "Get Kinect Tracked Bodies"))
static TArray<UNeoKinectBody*> GetTrackedBodies();
/**
* Gets tracked body whose Spine Base joint is nearest to the Kinect sensor.
*
* @param HasBody If there's at least one tracked body.
* @return The nearest body from the sensor. Null if no bodies are tracked.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body", meta = (DisplayName = "Get Kinect Nearest Body"))
static UNeoKinectBody* GetNearestBody(bool& HasBody);
/**
* Toggle the use of Color texture aligned joint locations and orientations.
*
* Calculating these remapped transforms introduce a small overhead, so it's
* disabled by default and reading the Color values from a Joint will return
* Zero values until this is enabled.
*
* @param bUse Enable usage of Joints' Color space transforms?
*/
UFUNCTION(BlueprintCallable, Category = "NeoKinect|Body", meta = (Keywords = "joints remap"))
static void SetUseJointsColorSpaceTransforms(const bool bUse);
/**
* Return if Joints' Color space transforms usage is enabled
*
* @return True if Joints' Color space transforms usage is enabled
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body")
static bool IsUsingJointsColorSpaceTransforms();
/**
* Cast a Body in the bodies array to its index number.
*
* @param Body The body instance.
* @return Body index.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body", Meta = (CompactNodeTitle = "->", Keywords = "convert cast"))
static int32 BodyToIndex(const UNeoKinectBody* Body);
/**
* Cast an index to a body from the bodies array.
*
* @param Index Index.
* @return Body instance. If index > 5 or index < 0, it's the body at index 0.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body", Meta = (CompactNodeTitle = "->", Keywords = "convert cast"))
static UNeoKinectBody* IndexToBody(const int32 Index);
/**
* Cast the Joint type to its joint index number.
*
* @param Joint Joint type.
* @return Index.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints", Meta = (CompactNodeTitle = "->", Keywords = "convert cast", BlueprintThreadSafe))
static int32 JointToIndex(const EKinectJointType Joint);
/**
* Cast a joint index to its Joint type.
*
* @param Index Index.
* @return Joint type. If index >= joint count or index < 0, it's Spine Base joint.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Body|Joints", Meta = (CompactNodeTitle = "->", Keywords = "convert cast"))
static EKinectJointType IndexToJoint(const int32 Index);
/*
* FACE TRACKING
*/
/**
* Array of faces (tracked and not tracked). The faces instances are updated
* automatically, so you don't need to request the array more than once.
*
* @return Faces array.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Face", meta = (DisplayName = "Get Kinect Faces"))
static TArray<UNeoKinectFace*> GetFaces();
/**
* Array with only the currently tracked faces.
*
* @return The currently tracked faces.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Face", meta = (DisplayName = "Get Kinect Tracked Faces"))
static TArray<UNeoKinectFace*> GetTrackedFaces();
/**
* Gets tracked face whose Head joint is nearest from the sensor.
*
* @param bHasAnyFace If there's at least one tracked face.
* @return The nearest face from the sensor. Null if no faces are tracked.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Face", meta = (DisplayName = "Get Kinect Nearest Face"))
static UNeoKinectFace* GetNearestFace(bool& bHasAnyFace);
/**
* Cast a Face to its index number.
*
* @param Face The face instance.
* @return Face index.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Face", Meta = (CompactNodeTitle = "->", Keywords = "convert cast"))
static int32 FaceToIndex(const UNeoKinectFace* Face);
/**
* Cast a face index to a Face object.
*
* @param Index Index.
* @return Face instance. If index > 5 or index < 0, it's the face at index 0.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Face", Meta = (CompactNodeTitle = "->", Keywords = "convert cast"))
static UNeoKinectFace* IndexToFace(const int32 Index);
/*
* CONSTANTS
*/
/**
* The number of bodies the Kinect device can track simultaneously.
*
* @return The number of bodies the Kinect device can track simultaneously.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Constants", meta = (Keywords = "length number size capability"))
static int32 GetTrackableBodyCount();
/**
* The number of faces the Kinect device can track simultaneously.
*
* @return The number of faces the Kinect device can track simultaneously.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Constants", meta = (Keywords = "length number size capability"))
static int32 GetTrackableFaceCount();
/**
* The number of joints the Kinect device can track in each tracked body.
*
* @return The number of joints the Kinect device can track in each tracked body.
*/
UFUNCTION(BlueprintPure, Category = "NeoKinect|Constants", meta = (Keywords = "length number size capability"))
static int32 GetJointCount();
/*////////////////////////////////////////////////////////////////////////////
BLUEPRINT FUNCTIONS END
////////////////////////////////////////////////////////////////////////////*/
private:
/** NeoKinect API sensor */
static NeoKinect::KinectSensor* Kinect;
/** Does the sensor polling */
static FKinectThread* KinectThread;
/** FTicker handle so we can stop ticking after disconnecting from the sensor */
static FTSTicker::FDelegateHandle TickerHandle;
//TODO: figure out a way to know when a level is unloaded
//static bool bAutoShutdown;
/** Should we read and update the bodies data each frame? */
static bool bUsingBodyFrame;
/** Should we remap the bodies transforms into 3D Color space each frame? */
static bool bUsingJointsColorSpaceTransforms;
/** Should we read and update the faces data each frame? */
static bool bUsingFaceFrame;
/** Initialize the bodies, faces and frames arrays */
static void InitStatics();
/** Updates bodies, faces and frames each frame */
static bool OnTick(float DeltaTime);
static bool IsUsingDepth();
static bool IsUsingBodyIndex();
static bool IsUsingColor();
/** Creates a 2D dynamic texture for frames usage */
static UTextureRenderTarget2D* CreateTexture(int32 Width, int32 Height, EPixelFormat PixelFormat, bool sRGB);
/** Updates all currently enabled frames. Executed in the Render thread */
static void UpdateFrames();
/** Delete frames textures memory */
static void ReleaseFrameResources(FFrameTextureData&);
/** Enable/disable bodies frame reading */
static void SetUseBodyFrame(bool bUse = true);
/** Converts bodies joints transforms to Unreal coordinate space */
static void ProcessBodies(const NeoKinect::KinectBody* KBodies);
/** Zero all bodies values. Used when disconnecting from the sensor */
static void ResetBodies();
/** Enable/disable faces frame reading */
static void SetUseFaceFrame(bool bUse = true);
/** Converts faces points transforms to Unreal coordinate space */
static void ProcessFaces(const NeoKinect::KinectFace* KFaces);
/** Zero all faces values. Used when disconnecting from the sensor */
static void ResetFaces();
UFUNCTION()
void BodyBeginTrackBroadcast(int32 BodyIndex);
UFUNCTION()
void BodyLostBroadcast(int32 BodyIndex);
UFUNCTION()
void FaceBeginTrackBroadcast(int32 FaceIndex);
UFUNCTION()
void FaceLostBroadcast(int32 FaceIndex);
};