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()。 |