From 28a90bb748e177ab6fb937e0d14763b1e4def4ae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Konstantin=20K=C3=BChlem?= <kuehlem@vr.rwth-aachen.de>
Date: Tue, 22 Apr 2025 10:09:34 +0200
Subject: [PATCH] fix: Set anim tick behavior for all VH related meshes
 automatically to avoid de-sync on NDisplay

---
 .../CharacterPlugin/Private/VirtualHuman.cpp  | 22 ++++++++++++++++---
 1 file changed, 19 insertions(+), 3 deletions(-)

diff --git a/Source/CharacterPlugin/Private/VirtualHuman.cpp b/Source/CharacterPlugin/Private/VirtualHuman.cpp
index f82ff653..23d52b2b 100644
--- a/Source/CharacterPlugin/Private/VirtualHuman.cpp
+++ b/Source/CharacterPlugin/Private/VirtualHuman.cpp
@@ -6,6 +6,7 @@
 #include "VirtualHumanAIController.h"
 #include "Engine/Engine.h"
 #include "CharacterPluginLogging.h"
+#include "HairStrandsInterface.h"
 
 AVirtualHuman::AVirtualHuman()
 {
@@ -36,6 +37,19 @@ void AVirtualHuman::BeginPlay()
 	// constantly seed the random stream so we don't have cluster syncing problems
 	// but seed it with the name of the VH, so random behavior is different for every VH
 	IdleRandomStream = FRandomStream(TextKeyUtil::HashString(GetName()));
+
+	// Ensure all mesh components tick no matter if they are rendered
+	// THis fixes de-synced animations on different cluster nodes
+	if (FModuleManager::Get().IsModuleLoaded("DisplayCluster")
+		&& IDisplayCluster::Get().GetOperationMode() == EDisplayClusterOperationMode::Cluster)
+	{
+		TArray<USkeletalMeshComponent*> Meshes;
+		GetComponents(Meshes, true);
+		for (USkeletalMeshComponent* M : Meshes)
+		{
+			M->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones;
+		}
+	}
 }
 
 // Called every frame
@@ -243,7 +257,7 @@ void AVirtualHuman::SetUpBodyType()
 		UObject* face_skeleton_obj = AssetLoader2.LoadSynchronous(AssetRef2);
 
 		if (root_obj != nullptr && body != nullptr && body_skeleton_obj != nullptr && face != nullptr &&
-		    face_skeleton_obj != nullptr)
+			face_skeleton_obj != nullptr)
 		{
 			USceneComponent* root = Cast<USceneComponent>(root_obj);
 			USkeleton* body_skel = Cast<USkeleton>(body_skeleton_obj);
@@ -332,7 +346,9 @@ void AVirtualHuman::PlayRandomIdleAnimation()
 			IdleAnim, "CustomIdleSlot", BlendTime, BlendTime);
 		// start timer for next random pick (make it a bit shorter to allow for blending, but only by a defined minimal amount for the animations)
 		GetWorld()->GetTimerManager().SetTimer(NextIdleTimerHandle, this, &AVirtualHuman::PlayRandomIdleAnimation,
-			DynamicMontage->GetPlayLength() - fmin(2.0 * BlendTime, (DynamicMontage->GetPlayLength()) * MinBlendTime), false);
+		                                       DynamicMontage->GetPlayLength() - fmin(
+			                                       2.0 * BlendTime, (DynamicMontage->GetPlayLength()) * MinBlendTime),
+		                                       false);
 	}
 	else
 	{
@@ -357,4 +373,4 @@ void AVirtualHuman::OnConstruction(const FTransform& Transform)
 {
 	Super::OnConstruction(Transform);
 	SetUpBodyType();
-}
\ No newline at end of file
+}
-- 
GitLab