513 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			513 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| 
								 | 
							
								# 相关类
							 | 
						|||
| 
								 | 
							
								- TsArkitDataReceiver(ArkitDataReceiver)
							 | 
						|||
| 
								 | 
							
								- TsChingmuMocapReceiverActor(ChingmuMocapReceiverActor)
							 | 
						|||
| 
								 | 
							
								- TsMotionReceiverActor(MotionReceiverActor) => BP_MotionReceiver:定义了MotionNetConfig.ini。
							 | 
						|||
| 
								 | 
							
								- TsMotionSenderActor(MotionSenderActor)
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								# TsChingmuMocapReceiverActor
							 | 
						|||
| 
								 | 
							
								***地图里只会有一个生成的TsChingmuMocapReceiverActor来管理动捕数据接收***
							 | 
						|||
| 
								 | 
							
								1. Init():在Server才会Spawn TsChingmuMocapReceiverActor。
							 | 
						|||
| 
								 | 
							
								2. ConnectChingMu():**ChingmuComp.StartConnectServer()**
							 | 
						|||
| 
								 | 
							
								3. Multicast_AligmMotionTime():寻找场景中的BP_MotionReceiver,并且调用Receiver.AlignTimeStamp()。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								## ChingmuMocapReceiverActor
							 | 
						|||
| 
								 | 
							
								核心逻辑:
							 | 
						|||
| 
								 | 
							
								- ***FChingmuThread::Run()***
							 | 
						|||
| 
								 | 
							
								- ***AChingmuMocapReceiverActor::Tick()***
							 | 
						|||
| 
								 | 
							
								- AChingmuMocapReceiverActor::DoSample()
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								```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();
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								 FChingmuThread::Run()中处理完[[#ST_MocapFrameData]]之后,将几个演员动补数据存入FrameQueue之后。在Tick()出队,之后数据存入AllHumanFrames/AllRigidBodyFrames。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								- AllHumanFrames
							 | 
						|||
| 
								 | 
							
									- ID
							 | 
						|||
| 
								 | 
							
									- std::vector<ST_MocapFrameData*> Frames
							 | 
						|||
| 
								 | 
							
										- ID
							 | 
						|||
| 
								 | 
							
										- TimeStamp
							 | 
						|||
| 
								 | 
							
										- FrameIndex
							 | 
						|||
| 
								 | 
							
										- BonesWorldPos
							 | 
						|||
| 
								 | 
							
										- BonesLocalRot
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								```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);//将帧数数据塞入对应HuamnID/RigidBodyID的AllHumanFrames/AllRigidBodyFrames中。
							 | 
						|||
| 
								 | 
							
											}
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									DoSample(AllHumanFrames);
							 | 
						|||
| 
								 | 
							
									DoSample(AllRigidBodyFrames);
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									// 每隔1s计算一次平均包间隔
							 | 
						|||
| 
								 | 
							
									if (CurTime - LastCheckIntervalTime > 1000)
							 | 
						|||
| 
								 | 
							
									{
							 | 
						|||
| 
								 | 
							
										if (AllHumanFrames.Num() > 0)
							 | 
						|||
| 
								 | 
							
										{
							 | 
						|||
| 
								 | 
							
											AllHumanFrames[0]->CalculatePackageAverageInterval(this->PackageAverageInterval);
							 | 
						|||
| 
								 | 
							
											LastCheckIntervalTime = CurTime;
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								### 采样相关逻辑
							 | 
						|||
| 
								 | 
							
								- ***SampleByTimeStamp***()
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								void AChingmuMocapReceiverActor::DoSample(TArray<MocapFrames*>& Frames)
							 | 
						|||
| 
								 | 
							
								{
							 | 
						|||
| 
								 | 
							
									for (auto i = 0; i < Frames.Num(); i++)
							 | 
						|||
| 
								 | 
							
									{
							 | 
						|||
| 
								 | 
							
										Frames[i]->CheckSize(CacheLimit);//判断当前帧数据是否超过指定长度(240帧,2~4秒数据),移除超出长度的数据。
							 | 
						|||
| 
								 | 
							
										if (SampleByTimeStamp(Frames[i]->Frames))//对数据进行插值,当前插值数据存在SampledHumanData。
							 | 
						|||
| 
								 | 
							
										{
							 | 
						|||
| 
								 | 
							
											SendFrameToCharacter();//执行对应的TsChingmuMocapReceiverActor.ts中的逻辑,主要是触发一个事件,讲数据传递给TsMotionRetargetComponent.ts 或者 TsSceneLiveLinkPropActor.ts(动捕道具)
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								class MocapFrames
							 | 
						|||
| 
								 | 
							
								{
							 | 
						|||
| 
								 | 
							
								public:
							 | 
						|||
| 
								 | 
							
									int ID;
							 | 
						|||
| 
								 | 
							
									std::vector<ST_MocapFrameData*> Frames = {};
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								public:
							 | 
						|||
| 
								 | 
							
									MocapFrames(): ID(0)
							 | 
						|||
| 
								 | 
							
									{
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									bool CheckSize(const int Limit)
							 | 
						|||
| 
								 | 
							
									{
							 | 
						|||
| 
								 | 
							
										if (Frames.size() > Limit)
							 | 
						|||
| 
								 | 
							
										{
							 | 
						|||
| 
								 | 
							
											const int DeletedCount = Frames.size() / 2;
							 | 
						|||
| 
								 | 
							
											for (auto i = 0; i < DeletedCount; i++)
							 | 
						|||
| 
								 | 
							
											{
							 | 
						|||
| 
								 | 
							
												auto Data = Frames[i];
							 | 
						|||
| 
								 | 
							
												if (Data)
							 | 
						|||
| 
								 | 
							
												{
							 | 
						|||
| 
								 | 
							
													delete Data;
							 | 
						|||
| 
								 | 
							
												}
							 | 
						|||
| 
								 | 
							
												Data = nullptr;
							 | 
						|||
| 
								 | 
							
											}
							 | 
						|||
| 
								 | 
							
											Frames.erase(Frames.cbegin(), Frames.cbegin() + DeletedCount);
							 | 
						|||
| 
								 | 
							
											return true;
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
										return false;
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
								};
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								对数据进行插值,当前插值数据存在**SampledHumanData**。
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								bool AChingmuMocapReceiverActor::SampleByTimeStamp(std::vector<ST_MocapFrameData*>& DataList)
							 | 
						|||
| 
								 | 
							
								{
							 | 
						|||
| 
								 | 
							
									const int64 SampleTime = ULiveDirectorStatics::GetUnixTime() - UMotionUtils::BackSampleTime;//UMotionUtils::BackSampleTime = 100ms,采样100ms的数据。
							 | 
						|||
| 
								 | 
							
									int Previous = -1;
							 | 
						|||
| 
								 | 
							
									int Next = -1;
							 | 
						|||
| 
								 | 
							
									for (int Index = DataList.size() - 1; Index > 0; Index--)//从Last => First遍历所有数据,确定插值的2个数据Index。
							 | 
						|||
| 
								 | 
							
									{
							 | 
						|||
| 
								 | 
							
										const ST_MocapFrameData* Data = DataList[Index];
							 | 
						|||
| 
								 | 
							
										if (Data == nullptr)
							 | 
						|||
| 
								 | 
							
										{
							 | 
						|||
| 
								 | 
							
											continue;
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
										if (Data->TimeStamp - SampleTime > 0)
							 | 
						|||
| 
								 | 
							
										{
							 | 
						|||
| 
								 | 
							
											Next = Index;
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
										else
							 | 
						|||
| 
								 | 
							
										{
							 | 
						|||
| 
								 | 
							
											Previous = Index;
							 | 
						|||
| 
								 | 
							
											break;
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									if (bShowSampleLog)
							 | 
						|||
| 
								 | 
							
									{
							 | 
						|||
| 
								 | 
							
										UE_LOG(LogTemp, Warning, TEXT("prev: %d, next: %d, total: %llu"), Previous, Next, DataList.size());
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
									if (Previous != -1 && Next != -1)
							 | 
						|||
| 
								 | 
							
									{
							 | 
						|||
| 
								 | 
							
										const auto p = DataList[Previous];
							 | 
						|||
| 
								 | 
							
										const auto n = DataList[Next];
							 | 
						|||
| 
								 | 
							
										const float Factor = (n->TimeStamp - p->TimeStamp) > 0
							 | 
						|||
| 
								 | 
							
											                     ? (1.0 * (SampleTime - p->TimeStamp) / (n->TimeStamp - p->TimeStamp))
							 | 
						|||
| 
								 | 
							
											                     : 1.0;
							 | 
						|||
| 
								 | 
							
										// Bone world pos cannot lerp like this
							 | 
						|||
| 
								 | 
							
										// It will cause bone length changes all the time
							 | 
						|||
| 
								 | 
							
										SampledHumanData->ID = p->ID;
							 | 
						|||
| 
								 | 
							
										SampledHumanData->TimeStamp = SampleTime;
							 | 
						|||
| 
								 | 
							
										SampledHumanData->FrameIndex = p->FrameIndex;
							 | 
						|||
| 
								 | 
							
										for (auto Index = 0; Index < 23; Index++)//对23个骨骼进行差值。
							 | 
						|||
| 
								 | 
							
										{
							 | 
						|||
| 
								 | 
							
											SampledHumanData->BonesWorldPos[Index] = UKismetMathLibrary::VLerp(
							 | 
						|||
| 
								 | 
							
												p->BonesWorldPos[Index], n->BonesWorldPos[Index], Factor);
							 | 
						|||
| 
								 | 
							
											SampledHumanData->BonesLocalRot[Index] = UKismetMathLibrary::RLerp(p->BonesLocalRot[Index].Rotator(),
							 | 
						|||
| 
								 | 
							
											                                                                   n->BonesLocalRot[Index].Rotator(),
							 | 
						|||
| 
								 | 
							
											                                                                   Factor, true).Quaternion();
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
										return true;
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
									if (Previous != -1)//容错处理,全都是Previous,数据太旧直接清空。
							 | 
						|||
| 
								 | 
							
									{
							 | 
						|||
| 
								 | 
							
										SampledHumanData->CopyFrom(DataList[Previous]);
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
										if(SampleTime - DataList[Previous]->TimeStamp > UMotionUtils::MotionTimeout)
							 | 
						|||
| 
								 | 
							
										{
							 | 
						|||
| 
								 | 
							
											// data is too old, clear the data list.
							 | 
						|||
| 
								 | 
							
											DataList.clear();
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
										return true;
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
									if (Next != -1)//没有Previous,直接复制Next的数据。
							 | 
						|||
| 
								 | 
							
									{
							 | 
						|||
| 
								 | 
							
										SampledHumanData->CopyFrom(DataList[Next]);
							 | 
						|||
| 
								 | 
							
										return true;
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
									return false;
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								### FChingmuThread
							 | 
						|||
| 
								 | 
							
								用途为:
							 | 
						|||
| 
								 | 
							
								- 获取当前系统时间。
							 | 
						|||
| 
								 | 
							
								- 使用异步Task的方式,通过调用**UChingMUComponent::FullBodyMotionCapBaseBonesLocalSpaceRotation()** 来更新每个演员的动捕数据。动捕数据存储在**ChingMUComponent**中的***LocalRotationList***、***GlobalLocationList***中。
							 | 
						|||
| 
								 | 
							
								- 管理HumanToLastReceiveTime,以此管理每个动捕演员的动画数据时长。
							 | 
						|||
| 
								 | 
							
								- OwnerActor->OnGetHumanData_NotInGameThread(),
							 | 
						|||
| 
								 | 
							
									- 根据当前时间与当前Frames,从UChingMUComponent中将数据复制到[[#ST_MocapFrameData]]中。
							 | 
						|||
| 
								 | 
							
									- 将[[#ST_MocapFrameData]]转换成JSON后,使用AMotionSenderActor::OnGetRawMocapData_NotInGameThread()发送。
							 | 
						|||
| 
								 | 
							
									- 将当前帧数据加入FrameQueue队列。
							 | 
						|||
| 
								 | 
							
								- 线程睡眠0.001s。以此保证AChingmuMocapReceiverActor::Tick()中可以把数据都处理完。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								```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);
							 | 
						|||
| 
								 | 
							
													}
							 | 
						|||
| 
								 | 
							
													if (HumanToLastReceiveTime[HumanIndex] != TmpTimeCode.Frames)//判断是否收到新的Frame数据
							 | 
						|||
| 
								 | 
							
													{
							 | 
						|||
| 
								 | 
							
														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;
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								## ST_MocapFrameData
							 | 
						|||
| 
								 | 
							
								- ST_MocapFrameData为动捕数据的原始帧数据。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								```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);
							 | 
						|||
| 
								 | 
							
											}
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
								};
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								# MotionCapture(青瞳插件)
							 | 
						|||
| 
								 | 
							
								实现1个组件与3个动画节点:
							 | 
						|||
| 
								 | 
							
								- [[#ChingMUComponent]]:
							 | 
						|||
| 
								 | 
							
								- [[#AnimNode_ChingMUPose]]:接受骨骼动捕数据。
							 | 
						|||
| 
								 | 
							
								- [[#AnimNode_ChingMURetargetPose]]:接受重定向后的骨骼动捕数据。
							 | 
						|||
| 
								 | 
							
								- AnimNode_ChingMURetargetPoseForBuild:
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								## ***ChingMUComponent***
							 | 
						|||
| 
								 | 
							
								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"。
							 | 
						|||
| 
								 | 
							
								3. [[#CalculateBoneCSRotation()]]
							 | 
						|||
| 
								 | 
							
								4. [[#FullBodyMotionCapBaseBonesLocalSpaceRotation]]
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								### CalculateBoneCSRotation
							 | 
						|||
| 
								 | 
							
								> 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,数组指针。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								### FullBodyMotionCapBaseBonesLocalSpaceRotation
							 | 
						|||
| 
								 | 
							
								相比CalculateBoneCSRotation,增加了时间码以及GlobalLocation的动捕数据获取。
							 | 
						|||
| 
								 | 
							
								1. m_motioncap->CMHuman():调用DLL的CMHumanExtern(),获取一个Double数组,前3个是RootLocation,后面全是Rotation。
							 | 
						|||
| 
								 | 
							
								2. motionCapturePlugin->CMHumanGlobalRTTC():调用DLL的CMHumanGlobalRTTC(),1-24 New Features。计算**VrpnTimeCode**以及**GlobalLocationList**。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								数据存在**ChingMUComponent**中的***LocalRotationList***、***GlobalLocationList***。
							 | 
						|||
| 
								 | 
							
								## 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()
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								# TsMotionReceiverActor
							 | 
						|||
| 
								 | 
							
								只在BeginPlay()中调用了this.MarkAsClientSeamlessTravel(); 具体逻辑在`AMotionReceiverActor`
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								## MotionReceiverActor
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								![[动捕逻辑思维导图.canvas]]
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								# Config与BoneName相关逻辑
							 | 
						|||
| 
								 | 
							
								1. Config/FullBodyConfig.json储存了对应的骨骼名称、Morph以及RootMotion骨骼名称。
							 | 
						|||
| 
								 | 
							
									1. 通过 UMotionUtils::GetModelBones()、UMotionUtils::GetMoveableBones()、UMotionUtils::GetMorphTargets()获取名称数组。
							 | 
						|||
| 
								 | 
							
								2. GetModelBones()
							 | 
						|||
| 
								 | 
							
									1. 主要在FAnimNode_FullBody::Initialize_AnyThread()被调用。
							 | 
						|||
| 
								 | 
							
									2. 填充`TArray<FBoneReference> BoneRefList;`,顺带初始化SampledFullBodyData。
							 | 
						|||
| 
								 | 
							
									3. InitBoneRefIndex(),初始化BoneRefList中每个FBoneReference的BoneIndex(通过骨骼名称找到),如果没有找到会提示对应的Log。
							 | 
						|||
| 
								 | 
							
									4. FAnimNode_FullBody::Evaluate_AnyThread(),作用在[[#ApplyDataToPose()]]。
							 | 
						|||
| 
								 | 
							
								3. GetMorphTargets()
							 | 
						|||
| 
								 | 
							
									1. 主要在FAnimNode_FullBody::Initialize_AnyThread()被调用。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								## ApplyDataToPose()
							 | 
						|||
| 
								 | 
							
								###  BoneTransform
							 | 
						|||
| 
								 | 
							
								1. 遍历BoneRefList(从UMotionUtils::GetModelBones()获得)
							 | 
						|||
| 
								 | 
							
								2. 对BoneIndex有效的骨骼进行一些操作。
							 | 
						|||
| 
								 | 
							
									1. 取得当前动画蓝图输出Pose的**骨骼Index**以及**采样后动捕数据的旋转值**。
							 | 
						|||
| 
								 | 
							
									2. 如果骨骼名是Hips,就将当前Index设置给HipsIndex。
							 | 
						|||
| 
								 | 
							
									3. 将旋转值应用到OutputPose中。
							 | 
						|||
| 
								 | 
							
									4. 判断当前骨骼名是否为MoveableBones中的名称,将这些骨骼的Location设置到OutputPose中。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								### MorphValues
							 | 
						|||
| 
								 | 
							
								将对应MorphTarget数据应用到对应的CurveChannel上。
							 | 
						|||
| 
								 | 
							
								### RootMotion
							 | 
						|||
| 
								 | 
							
								根据bUseHipsTranslation变量执行不同的逻辑:
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								#### MapTranslationToHips
							 | 
						|||
| 
								 | 
							
								调用函数形参如下:
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								MapTranslationToHips(Output, EvaluatedFullBodyData, 0, HipsIndex);
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								1. 获取Joints骨骼的Locaiton作为RootMotion数据
							 | 
						|||
| 
								 | 
							
								2. 判断Joints骨骼是不是根骨骼,如果**是**则调整RootMotion数据轴向。
							 | 
						|||
| 
								 | 
							
								3. 将Joints骨骼的Location归零。
							 | 
						|||
| 
								 | 
							
								4. 如果Hips骨骼有效,则将RootMotion数据加到其Location上。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								#### ExtractRootMotionInfo
							 | 
						|||
| 
								 | 
							
								1. 获取Joints骨骼的Locaiton作为RootMotion数据。
							 | 
						|||
| 
								 | 
							
								2. 判断Joints骨骼是不是根骨骼,如果**不是**则调整RootMotion数据轴向。(**轴向与MapTranslationToHips()不同**)
							 | 
						|||
| 
								 | 
							
								3. 将Joints骨骼的Location归零。
							 | 
						|||
| 
								 | 
							
								4. 将RootMotion设置给AnimInstance的RootMotionLocation。
							 | 
						|||
| 
								 | 
							
								5. 如果Hips骨骼有效,进行一堆计算,最终将Rotation设置AnimInstance的RootMotionRotation。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								# 采样接收数据
							 | 
						|||
| 
								 | 
							
								- [x] 确定ChingMu重定向后Transform数组长度。
							 | 
						|||
| 
								 | 
							
									- 数据长度150个,但中间有很多事空值。
							 | 
						|||
| 
								 | 
							
								- [ ] 
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								TArray<MocapFrames*>
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								使用cmr:二郎拖。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								# UE4移植Mocap问题确定
							 | 
						|||
| 
								 | 
							
								ASoul的Control有一步摆初始Pose的操作,让下列骨骼的初始化Pose发生了改变。
							 | 
						|||
| 
								 | 
							
								Spine UE4角度<33.144 0.356 0.649> => UE5角度<33.418,-1.266,0.351>
							 | 
						|||
| 
								 | 
							
								Spine~Spine3
							 | 
						|||
| 
								 | 
							
								Chest
							 | 
						|||
| 
								 | 
							
								ChestMid
							 | 
						|||
| 
								 | 
							
								Neck1
							 | 
						|||
| 
								 | 
							
								Head
							 | 
						|||
| 
								 | 
							
								LeftArm
							 | 
						|||
| 
								 | 
							
								LeftForeArm
							 | 
						|||
| 
								 | 
							
								LeftHand
							 | 
						|||
| 
								 | 
							
								RightArm
							 | 
						|||
| 
								 | 
							
								RightForeArm
							 | 
						|||
| 
								 | 
							
								RightHand
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								# UE4 ChingMu重定向
							 | 
						|||
| 
								 | 
							
								- 移植FullBody的以下语句。
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								bGetMotionData = Recv->SampleFullBodyData_AnimationThread(ValidIdentity,  
							 | 
						|||
| 
								 | 
							
								                                                          ULiveDirectorStatics::GetUnixTime() -  
							 | 
						|||
| 
								 | 
							
								                                                          UMotionUtils::BackSampleTime * 2,  
							 | 
						|||
| 
								 | 
							
								                                                          SampledFullBodyData);
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								## 
							 | 
						|||
| 
								 | 
							
								- AChingmuMocapReceiverActor(创建线程)=>
							 | 
						|||
| 
								 | 
							
								- FChingmuThread(负责接收数据并且塞入AChingmuMocapReceiverActor的FrameQueue)=>
							 | 
						|||
| 
								 | 
							
									- [x] 添加时间轴判断,避免加入重复的帧。
							 | 
						|||
| 
								 | 
							
								- AChingmuMocapReceiverActor(Tick)=>
							 | 
						|||
| 
								 | 
							
									- 从FrameQueue提取动捕帧数据并且塞入`TArray<MocapRetargetFrames*> AllHumanFrames`(PutMocapDataIntoFrameList())。
							 | 
						|||
| 
								 | 
							
									- DoSample()
							 | 
						|||
| 
								 | 
							
										1. SampleByTimeStamp():对所有帧进行采样。
							 | 
						|||
| 
								 | 
							
										2. SendFrameToCharacter():逻辑在Puerts中,发送给MotionProcess动捕数据。
							 | 
						|||
| 
								 | 
							
									- CalculatePackageAverageInterval():相关逻辑感觉没用。
							 | 
						|||
| 
								 | 
							
								- FAnimNode_FullBody =>
							 | 
						|||
| 
								 | 
							
									- Update_AnyThread():bGetMotionData = Recv->SampleFullBodyData_AnimationThread():取得对应HumanID的动捕数据。
							 | 
						|||
| 
								 | 
							
									- Evaluate_AnyThread():取得SampledFullBodyData => ApplyDataToPose()。
							 |