2024-10-23 16:38:20 +08:00
|
|
|
|
# 相关类
|
|
|
|
|
- TsArkitDataReceiver(ArkitDataReceiver)
|
|
|
|
|
- TsChingmuMocapReceiverActor(ChingmuMocapReceiverActor)
|
|
|
|
|
- TsMotionReceiverActor(MotionReceiverActor) => BP_MotionReceiver:定义了MotionNetConfig.ini。
|
|
|
|
|
- TsMotionSenderActor(MotionSenderActor)
|
|
|
|
|
|
|
|
|
|
# TsChingmuMocapReceiverActor
|
2024-10-30 16:38:24 +08:00
|
|
|
|
***地图里只会有一个生成的TsChingmuMocapReceiverActor来管理动捕数据接收***
|
2024-10-23 16:38:20 +08:00
|
|
|
|
1. Init():在Server才会Spawn TsChingmuMocapReceiverActor。
|
|
|
|
|
2. ConnectChingMu():**ChingmuComp.StartConnectServer()**
|
|
|
|
|
3. Multicast_AligmMotionTime():寻找场景中的BP_MotionReceiver,并且调用Receiver.AlignTimeStamp()。
|
|
|
|
|
|
|
|
|
|
## ChingmuMocapReceiverActor
|
2024-10-30 16:38:24 +08:00
|
|
|
|
核心逻辑:
|
|
|
|
|
- ***FChingmuThread::Run()***
|
|
|
|
|
- ***AChingmuMocapReceiverActor::Tick()***
|
|
|
|
|
- AChingmuMocapReceiverActor::DoSample()
|
|
|
|
|
|
2024-10-28 19:23:10 +08:00
|
|
|
|
```c++
|
|
|
|
|
void AChingmuMocapReceiverActor::BeginPlay()
|
|
|
|
|
{
|
|
|
|
|
Super::BeginPlay();
|
|
|
|
|
MaxHumanCount = 10;
|
|
|
|
|
MaxRigidBodyCount = 10;
|
|
|
|
|
CacheLimit = 240;
|
|
|
|
|
SampledHumanData = NewObject<UMocapFrameData>();
|
|
|
|
|
ThreadInterval = 0.002;
|
|
|
|
|
BackIndexCount = int64(UMotionUtils::BackSampleTime / (1000.0 / CHINGMU_SERVER_FPS));//BackSampleTime = 100ms CHINGMU_SERVER_FPS =120ms
|
|
|
|
|
ChingmuComp = Cast<UChingMUComponent>(GetComponentByClass(UChingMUComponent::StaticClass()));
|
|
|
|
|
if (ChingmuComp == nullptr)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogTemp, Error, TEXT("Chingmu Component is missing!!"));
|
|
|
|
|
}
|
|
|
|
|
Thread = new FChingmuThread("Chingmu Data Thread", this);
|
|
|
|
|
Sender = GetMotionSender();
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2024-10-30 16:38:24 +08:00
|
|
|
|
|
|
|
|
|
```c++
|
|
|
|
|
void AChingmuMocapReceiverActor::Tick(float DeltaTime)
|
|
|
|
|
{
|
|
|
|
|
Super::Tick(DeltaTime);
|
|
|
|
|
|
|
|
|
|
if(!Sender)
|
|
|
|
|
{
|
|
|
|
|
Sender = GetMotionSender();
|
|
|
|
|
}
|
|
|
|
|
const auto CurTime = ULiveDirectorStatics::GetUnixTime();
|
|
|
|
|
if(UseThread)
|
|
|
|
|
{
|
|
|
|
|
// 线程方式
|
|
|
|
|
// 在数据队列中获取青瞳数据
|
|
|
|
|
while (!FrameQueue.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
ST_MocapFrameData* Frame;
|
|
|
|
|
if (FrameQueue.Dequeue(Frame))
|
|
|
|
|
{
|
|
|
|
|
PutMocapDataIntoFrameList(Frame);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DoSample(AllHumanFrames);
|
|
|
|
|
DoSample(AllRigidBodyFrames);
|
|
|
|
|
|
|
|
|
|
// 每隔1s计算一次平均包间隔
|
|
|
|
|
if (CurTime - LastCheckIntervalTime > 1000)
|
|
|
|
|
{
|
|
|
|
|
if (AllHumanFrames.Num() > 0)
|
|
|
|
|
{
|
|
|
|
|
AllHumanFrames[0]->CalculatePackageAverageInterval(this->PackageAverageInterval);
|
|
|
|
|
LastCheckIntervalTime = CurTime;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```c++
|
|
|
|
|
void AChingmuMocapReceiverActor::DoSample(TArray<MocapFrames*>& Frames)
|
|
|
|
|
{
|
|
|
|
|
for (auto i = 0; i < Frames.Num(); i++)
|
|
|
|
|
{
|
|
|
|
|
Frames[i]->CheckSize(CacheLimit);
|
|
|
|
|
if (SampleByTimeStamp(Frames[i]->Frames))
|
|
|
|
|
{
|
|
|
|
|
SendFrameToCharacter();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
2024-10-28 19:23:10 +08:00
|
|
|
|
### FChingmuThread
|
|
|
|
|
用途为:
|
2024-10-30 16:38:24 +08:00
|
|
|
|
- 获取当前系统时间。
|
|
|
|
|
- 使用异步Task的方式,通过调用**UChingMUComponent::FullBodyMotionCapBaseBonesLocalSpaceRotation()** 来更新每个演员的动捕数据。动捕数据存储在**ChingMUComponent**中的***LocalRotationList***、***GlobalLocationList***中。
|
|
|
|
|
- 管理HumanToLastReceiveTime,以此管理每个动捕演员的动画数据时长。
|
|
|
|
|
- OwnerActor->OnGetHumanData_NotInGameThread(),
|
|
|
|
|
- 根据当前时间与当前Frames,从UChingMUComponent中将数据复制到[[#ST_MocapFrameData]]中。
|
|
|
|
|
- 将[[#ST_MocapFrameData]]转换成JSON后,使用AMotionSenderActor::OnGetRawMocapData_NotInGameThread()发送。
|
|
|
|
|
- 将当前帧数据加入FrameQueue队列。
|
|
|
|
|
- 线程睡眠0.001s。
|
2024-10-28 19:23:10 +08:00
|
|
|
|
|
|
|
|
|
```c++
|
|
|
|
|
uint32 FChingmuThread::Run()
|
|
|
|
|
{
|
|
|
|
|
FTransform Tmp;
|
|
|
|
|
while (bRun)
|
|
|
|
|
{
|
|
|
|
|
if (OwnerActor && OwnerActor->UseThread && OwnerActor->ChingmuComp && OwnerActor->ChingmuComp->IsConnected())
|
|
|
|
|
{
|
|
|
|
|
CurTime = ULiveDirectorStatics::GetUnixTime();
|
|
|
|
|
// Human
|
|
|
|
|
for (auto HumanIndex = 0; HumanIndex < OwnerActor->MaxHumanCount; HumanIndex++)
|
|
|
|
|
{
|
|
|
|
|
const auto bRes = OwnerActor->ChingmuComp->FullBodyMotionCapBaseBonesLocalSpaceRotation(
|
|
|
|
|
OwnerActor->ChingmuFullAddress, HumanIndex, TmpTimeCode);
|
|
|
|
|
if (bRes)
|
|
|
|
|
{
|
|
|
|
|
if (!HumanToLastReceiveTime.Contains(HumanIndex))//空数据处理。
|
|
|
|
|
{
|
|
|
|
|
HumanToLastReceiveTime.Add(HumanIndex, 0);
|
|
|
|
|
}
|
2024-10-30 16:38:24 +08:00
|
|
|
|
if (HumanToLastReceiveTime[HumanIndex] != TmpTimeCode.Frames)//判断是否收到新的Frame数据
|
2024-10-28 19:23:10 +08:00
|
|
|
|
{
|
|
|
|
|
HumanToLastReceiveTime[HumanIndex] = TmpTimeCode.Frames;
|
|
|
|
|
OwnerActor->OnGetHumanData_NotInGameThread(HumanIndex, CurTime, TmpTimeCode.Frames);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// get same frame, skip
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Rigidbody
|
|
|
|
|
|
|
|
|
|
for (auto RigidBodyIndex = OwnerActor->RigidBodyStartIndex; RigidBodyIndex < OwnerActor->RigidBodyStartIndex
|
|
|
|
|
+ OwnerActor->MaxRigidBodyCount; RigidBodyIndex++)
|
|
|
|
|
{
|
|
|
|
|
OwnerActor->ChingmuComp->GetTrackerPoseTC(OwnerActor->ChingmuFullAddress, RigidBodyIndex, Tmp,
|
|
|
|
|
TmpTimeCode);
|
|
|
|
|
|
|
|
|
|
if (!RigidBodyToLastReceiveTransform.Contains(RigidBodyIndex))
|
|
|
|
|
{
|
|
|
|
|
RigidBodyToLastReceiveTransform.Add(RigidBodyIndex, FTransform::Identity);
|
|
|
|
|
}
|
|
|
|
|
// 道具的TmpTimeCode.Frames永远为0,所以无法用帧数判断
|
|
|
|
|
// 改为transform判断
|
|
|
|
|
if (!RigidBodyToLastReceiveTransform[RigidBodyIndex].Equals(Tmp))
|
|
|
|
|
{
|
|
|
|
|
RigidBodyToLastReceiveTransform[RigidBodyIndex] = Tmp;
|
|
|
|
|
OwnerActor->OnGetRigidBodyData_NotInGameThread(RigidBodyIndex, Tmp, CurTime, TmpTimeCode.Frames);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (bRun)
|
|
|
|
|
{
|
|
|
|
|
FPlatformProcess::Sleep(OwnerActor ? OwnerActor->ThreadInterval : 0.004);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
UE_LOG(LogTemp, Warning, TEXT("%s finish work."), *ThreadName)
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2024-10-30 16:38:24 +08:00
|
|
|
|
## ST_MocapFrameData
|
2024-10-28 19:23:10 +08:00
|
|
|
|
```c++
|
|
|
|
|
#define MOCAP_BONE_COUNT 23
|
|
|
|
|
|
|
|
|
|
enum E_MotionType
|
|
|
|
|
{
|
|
|
|
|
Human,
|
|
|
|
|
RigidBody
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum E_SourceType
|
|
|
|
|
{
|
|
|
|
|
Mocap,
|
|
|
|
|
CMR,
|
|
|
|
|
Replay
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct ST_MocapFrameData
|
|
|
|
|
{
|
|
|
|
|
int ID;
|
|
|
|
|
int64 TimeStamp;
|
|
|
|
|
int FrameIndex;
|
|
|
|
|
E_MotionType MotionType;
|
|
|
|
|
E_SourceType SourceType;
|
|
|
|
|
FVector BonesWorldPos[MOCAP_BONE_COUNT];
|
|
|
|
|
FQuat BonesLocalRot[MOCAP_BONE_COUNT];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class LIVEDIRECTOR_API UMocapFrameData : public UObject
|
|
|
|
|
{
|
|
|
|
|
GENERATED_BODY()
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
|
|
|
|
int ID;
|
|
|
|
|
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
|
|
|
|
TArray<FVector> BonesWorldPos;
|
|
|
|
|
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
|
|
|
|
TArray<FQuat> BonesLocalRot;
|
|
|
|
|
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
|
|
|
|
int64 TimeStamp;
|
|
|
|
|
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
|
|
|
|
int FrameIndex;
|
|
|
|
|
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
|
|
|
|
int MotionType; // 0 human; 1 rigidbody
|
|
|
|
|
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
|
|
|
|
int SourceType; // 0 mocap, 1 cmr
|
|
|
|
|
public:
|
|
|
|
|
void CopyFrom(const ST_MocapFrameData* Other)
|
|
|
|
|
{
|
|
|
|
|
ID = Other->ID;
|
|
|
|
|
TimeStamp = Other->TimeStamp;
|
|
|
|
|
FrameIndex = Other->FrameIndex;
|
|
|
|
|
MotionType = Other->MotionType;
|
|
|
|
|
SourceType = Other->SourceType;
|
|
|
|
|
for (auto Index = 0; Index < 23; Index++)
|
|
|
|
|
{
|
|
|
|
|
BonesWorldPos[Index] = Other->BonesWorldPos[Index];
|
|
|
|
|
BonesLocalRot[Index] = Other->BonesLocalRot[Index];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class MocapFrames
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
int ID;
|
|
|
|
|
std::vector<ST_MocapFrameData*> Frames = {};
|
|
|
|
|
|
|
|
|
|
void CalculatePackageAverageInterval(float& Res)
|
|
|
|
|
{
|
|
|
|
|
if(Frames.size() > 0)
|
|
|
|
|
{
|
|
|
|
|
auto First = Frames[0];
|
|
|
|
|
auto Last = Frames[Frames.size() - 1];
|
|
|
|
|
if(Last->FrameIndex > First->FrameIndex)
|
|
|
|
|
{
|
|
|
|
|
Res = 1.0 * (Last->TimeStamp - First->TimeStamp) / (Last->FrameIndex - First->FrameIndex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
```
|
2024-10-30 12:48:27 +08:00
|
|
|
|
# MotionCapture(青瞳插件)
|
|
|
|
|
实现1个组件与3个动画节点:
|
|
|
|
|
- [[#ChingMUComponent]]:
|
|
|
|
|
- [[#AnimNode_ChingMUPose]]:接受骨骼动捕数据。
|
|
|
|
|
- [[#AnimNode_ChingMURetargetPose]]:接受重定向后的骨骼动捕数据。
|
|
|
|
|
- AnimNode_ChingMURetargetPoseForBuild:
|
|
|
|
|
|
|
|
|
|
## ***ChingMUComponent***
|
2024-10-30 14:14:00 +08:00
|
|
|
|
1. Init
|
|
|
|
|
1. BeginPlay():取得ini文件中的配置信息;取得当前角色的SkeletonMesh => CharacterSkinMesh;取得BoneName=>BoneIndex Map、TPose状态下骨骼的旋转值、TposeParentBonesRotation。
|
|
|
|
|
2. Connect
|
|
|
|
|
1. StartConnectServer():motionCapturePlugin->ConnectCommand = "ConnectServer"。具体逻辑会在FMotionCapture::Tick()处理。
|
|
|
|
|
2. DisConnectServer():motionCapturePlugin->ConnectCommand = "DisConnect"。
|
2024-10-30 16:38:24 +08:00
|
|
|
|
3. [[#CalculateBoneCSRotation()]]
|
|
|
|
|
4. [[#FullBodyMotionCapBaseBonesLocalSpaceRotation]]
|
2024-10-30 14:14:00 +08:00
|
|
|
|
|
|
|
|
|
### CalculateBoneCSRotation
|
2024-10-30 16:38:24 +08:00
|
|
|
|
> Get Human Fullbody Tracker data ,including of 23joints localRotation and root joint world Position
|
|
|
|
|
|
|
|
|
|
1. m_motioncap->CMHuman():调用DLL的CMHumanExtern(),获取一个Double数组,前3个是RootLocation,后面全是Rotation。
|
|
|
|
|
2. 计算最终的四元数旋转值。
|
|
|
|
|
3. 返回的形参 FQuat* BonesComponentSpaceRotation,数组指针。
|
2024-10-30 14:14:00 +08:00
|
|
|
|
|
2024-10-30 16:38:24 +08:00
|
|
|
|
### FullBodyMotionCapBaseBonesLocalSpaceRotation
|
|
|
|
|
相比CalculateBoneCSRotation,增加了时间码以及GlobalLocation的动捕数据获取。
|
|
|
|
|
1. m_motioncap->CMHuman():调用DLL的CMHumanExtern(),获取一个Double数组,前3个是RootLocation,后面全是Rotation。
|
|
|
|
|
2. motionCapturePlugin->CMHumanGlobalRTTC():调用DLL的CMHumanGlobalRTTC(),1-24 New Features。计算**VrpnTimeCode**以及**GlobalLocationList**。
|
2024-10-30 12:48:27 +08:00
|
|
|
|
|
2024-10-30 16:38:24 +08:00
|
|
|
|
数据存在**ChingMUComponent**中的***LocalRotationList***、***GlobalLocationList***。
|
2024-10-30 12:48:27 +08:00
|
|
|
|
## FAnimNode_ChingMUPose
|
|
|
|
|
1. Initialize_AnyThread():取得**ChingMUComponent**。
|
|
|
|
|
2. Update_AnyThread():调用**ChingMUComponent->CalculateBoneCSRotation()**
|
|
|
|
|
3. Evaluate_AnyThread():对23根骨骼进行遍历;取得RefPose后,将从Update_AnyThread()获得动捕数据(**Rotation**)覆盖到上面(ComponentSpace),**根骨骼需要额外添加Location数据**。最后将数据从ComponentSpace => LocalSpace。
|
|
|
|
|
|
|
|
|
|
## AnimNode_ChingMURetargetPose
|
|
|
|
|
1. Initialize_AnyThread():创建曲线逻辑(TCHour、TCMinute、TCSecond、TCFrame)。
|
|
|
|
|
2. Update_AnyThread():
|
|
|
|
|
3. Evaluate_AnyThread():相关逻辑都实现在这里。
|
|
|
|
|
|
|
|
|
|
### AnimNode_ChingMURetargetPose::Evaluate_AnyThread()
|
|
|
|
|
|
2024-10-23 16:38:20 +08:00
|
|
|
|
# TsMotionReceiverActor
|
|
|
|
|
只在BeginPlay()中调用了this.MarkAsClientSeamlessTravel(); 具体逻辑在`AMotionReceiverActor`
|
|
|
|
|
|
|
|
|
|
## MotionReceiverActor
|
2024-10-28 19:23:10 +08:00
|
|
|
|
|