From fc4eda4ec188c733e2687a260bab8075adfc5d9f Mon Sep 17 00:00:00 2001
From: Kris Helwig <helwig@vr.rwth-aachen.de>
Date: Fri, 7 Mar 2025 14:52:47 +0100
Subject: [PATCH 01/14] feat(pawn): Adds VR scaling functionality

---
 .../RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp | 59 +++++++++++++++----
 Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h |  4 ++
 2 files changed, 50 insertions(+), 13 deletions(-)

diff --git a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
index 812e83ea..4eb03b04 100644
--- a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
+++ b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
@@ -41,8 +41,30 @@ ARWTHVRPawn::ARWTHVRPawn(const FObjectInitializer& ObjectInitializer) : Super(Ob
 
 	LeftHand = CreateDefaultSubobject<UReplicatedMotionControllerComponent>(TEXT("Left Hand MCC"));
 	LeftHand->SetupAttachment(RootComponent);
+
+	GetRootComponent()->TransformUpdated.AddLambda([this](USceneComponent*, EUpdateTransformFlags, ETeleportType)
+	{
+		FVector CurrentScale = this->GetActorScale3D();
+		if (CurrentScale.X == CurrentScale.Y && CurrentScale.Y == CurrentScale.Z)
+		{
+			float expectedScale = GetWorldSettings()->WorldToMeters / InitialWorldToMeters;
+			float ErrorPrecision = 1E-05;
+			if (FMath::IsNearlyEqual(CurrentScale.X, expectedScale, ErrorPrecision))
+			{
+				return;
+			}
+		}
+		UE_LOGFMT(Toolkit, Warning,
+		          "ARWTHVRPawn: Do not adjust the scale of the pawn directly. This will not work in VR. Use ARWTHVRPawn::SetScale(float) instead.")
+		;
+	});
+}
+
+void ARWTHVRPawn::BeginPlay()
+{
+	Super::BeginPlay();
+	InitialWorldToMeters = GetWorldSettings()->WorldToMeters;
 }
-void ARWTHVRPawn::BeginPlay() { Super::BeginPlay(); }
 
 void ARWTHVRPawn::Tick(float DeltaSeconds)
 {
@@ -56,6 +78,17 @@ void ARWTHVRPawn::Tick(float DeltaSeconds)
 	EvaluateLivelink();
 }
 
+/*
+ *	Scales the Pawn while also adjusting the WorldToMeters ratio to adjust for pupillary distance.
+ *	Only supports uniform scaling.
+ */
+void ARWTHVRPawn::SetScale(float NewScale)
+{
+	FVector NewScaleVector = FVector(NewScale, NewScale, NewScale);
+	GetWorldSettings()->WorldToMeters = InitialWorldToMeters * NewScale;
+	SetActorScale3D(NewScaleVector);
+}
+
 /*
  * The alternative would be to do this only on the server on possess and check for player state/type,
  * as connections now send their playertype over.
@@ -71,7 +104,7 @@ void ARWTHVRPawn::NotifyControllerChanged()
 	if (HasAuthority())
 	{
 		UE_LOG(Toolkit, Display,
-			   TEXT("ARWTHVRPawn: Player Controller has changed, trying to change Cluster attachment if possible..."));
+		       TEXT("ARWTHVRPawn: Player Controller has changed, trying to change Cluster attachment if possible..."));
 		if (const ARWTHVRPlayerState* State = GetPlayerState<ARWTHVRPlayerState>())
 		{
 			const EPlayerType Type = State->GetPlayerType();
@@ -101,7 +134,7 @@ void ARWTHVRPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponen
 	}
 
 	UE_LOGFMT(Toolkit, Display, "SetupPlayerInputComponent: Player Controller is valid, setting up input for {Pawn}",
-			  GetName());
+	          GetName());
 
 
 	// Set the control rotation of the PC to zero again. There is a small period of 2 frames where, when the pawn gets
@@ -173,7 +206,7 @@ void ARWTHVRPawn::AddInputMappingContext(const APlayerController* PC, const UInp
 			else
 			{
 				UE_LOGFMT(Toolkit, Warning,
-						  "ARWTHVRPawn::AddInputMappingContext: UEnhancedInputLocalPlayerSubsystem is nullptr!");
+				          "ARWTHVRPawn::AddInputMappingContext: UEnhancedInputLocalPlayerSubsystem is nullptr!");
 			}
 		}
 		else
@@ -201,7 +234,7 @@ void ARWTHVRPawn::EvaluateLivelink() const
 			IModularFeatures::Get().GetModularFeature<ILiveLinkClient>(ILiveLinkClient::ModularFeatureName);
 		FLiveLinkSubjectFrameData SubjectData;
 		const bool bHasValidData = LiveLinkClient.EvaluateFrame_AnyThread(HeadSubjectRepresentation.Subject,
-																		  HeadSubjectRepresentation.Role, SubjectData);
+		                                                                  HeadSubjectRepresentation.Role, SubjectData);
 
 		if (!bHasValidData)
 		{
@@ -278,12 +311,12 @@ void ARWTHVRPawn::AttachClustertoPawn()
 		bool bAttached = ClusterActor->AttachToComponent(GetRootComponent(), AttachmentRules);
 		// State->GetCorrespondingClusterActor()->OnAttached();
 		UE_LOGFMT(Toolkit, Display,
-				  "ARWTHVRPawn: Attaching corresponding cluster actor to our pawn returned: {Attached}", bAttached);
+		          "ARWTHVRPawn: Attaching corresponding cluster actor to our pawn returned: {Attached}", bAttached);
 	}
 	else
 	{
 		UE_LOGFMT(Toolkit, Error,
-				  "ARWTHVRPawn::AttachClustertoPawn: No ARWTHVRPlayerState set! This won't work on the Cave.");
+		          "ARWTHVRPawn::AttachClustertoPawn: No ARWTHVRPlayerState set! This won't work on the Cave.");
 	}
 
 	if (HasAuthority()) // Should always be the case here, but double check
@@ -321,19 +354,19 @@ void ARWTHVRPawn::SetCameraOffset() const
 }
 
 void ARWTHVRPawn::ApplyLiveLinkTransform(const FTransform& Transform,
-										 const FLiveLinkTransformStaticData& StaticData) const
+                                         const FLiveLinkTransformStaticData& StaticData) const
 {
 	if (StaticData.bIsLocationSupported)
 	{
 		if (bWorldTransform)
 		{
 			HeadCameraComponent->SetWorldLocation(Transform.GetLocation(), false, nullptr,
-												  ETeleportType::TeleportPhysics);
+			                                      ETeleportType::TeleportPhysics);
 		}
 		else
 		{
 			HeadCameraComponent->SetRelativeLocation(Transform.GetLocation(), false, nullptr,
-													 ETeleportType::TeleportPhysics);
+			                                         ETeleportType::TeleportPhysics);
 		}
 	}
 
@@ -342,12 +375,12 @@ void ARWTHVRPawn::ApplyLiveLinkTransform(const FTransform& Transform,
 		if (bWorldTransform)
 		{
 			HeadCameraComponent->SetWorldRotation(Transform.GetRotation(), false, nullptr,
-												  ETeleportType::TeleportPhysics);
+			                                      ETeleportType::TeleportPhysics);
 		}
 		else
 		{
 			HeadCameraComponent->SetRelativeRotation(Transform.GetRotation(), false, nullptr,
-													 ETeleportType::TeleportPhysics);
+			                                         ETeleportType::TeleportPhysics);
 		}
 	}
 
@@ -362,4 +395,4 @@ void ARWTHVRPawn::ApplyLiveLinkTransform(const FTransform& Transform,
 			HeadCameraComponent->SetRelativeScale3D(Transform.GetScale3D());
 		}
 	}
-}
+}
\ No newline at end of file
diff --git a/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h b/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h
index cd81e497..eaf24745 100644
--- a/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h
+++ b/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h
@@ -31,6 +31,9 @@ public:
 
 	virtual void NotifyControllerChanged() override;
 
+	UFUNCTION(BlueprintCallable)
+	void SetScale(float NewScale);
+	
 	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Pawn|Input")
 	TArray<UInputMappingContext*> InputMappingContexts;
 
@@ -111,4 +114,5 @@ protected:
 
 private:
 	UInputComponent* ActivePlayerInputComponent;
+	float InitialWorldToMeters;
 };
-- 
GitLab


From 9b1eb745890b8e34f447ecc96eac826efbd57d7d Mon Sep 17 00:00:00 2001
From: Kris Helwig <helwig@vr.rwth-aachen.de>
Date: Tue, 11 Mar 2025 15:42:58 +0100
Subject: [PATCH 02/14] feat(pawn): Adds OnScaleChanged callback

---
 .../RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp | 29 ++++++++++---------
 Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h | 13 +++++++--
 2 files changed, 27 insertions(+), 15 deletions(-)

diff --git a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
index 4eb03b04..7e32a90d 100644
--- a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
+++ b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
@@ -42,21 +42,16 @@ ARWTHVRPawn::ARWTHVRPawn(const FObjectInitializer& ObjectInitializer) : Super(Ob
 	LeftHand = CreateDefaultSubobject<UReplicatedMotionControllerComponent>(TEXT("Left Hand MCC"));
 	LeftHand->SetupAttachment(RootComponent);
 
+	UniformScale = GetActorScale3D().X;
 	GetRootComponent()->TransformUpdated.AddLambda([this](USceneComponent*, EUpdateTransformFlags, ETeleportType)
 	{
 		FVector CurrentScale = this->GetActorScale3D();
-		if (CurrentScale.X == CurrentScale.Y && CurrentScale.Y == CurrentScale.Z)
+		if (CurrentScale.X != UniformScale || CurrentScale.Y != UniformScale || CurrentScale.Z != UniformScale)
 		{
-			float expectedScale = GetWorldSettings()->WorldToMeters / InitialWorldToMeters;
-			float ErrorPrecision = 1E-05;
-			if (FMath::IsNearlyEqual(CurrentScale.X, expectedScale, ErrorPrecision))
-			{
-				return;
-			}
+			UE_LOGFMT(Toolkit, Warning,
+			          "ARWTHVRPawn: Do not adjust the scale of the pawn directly. This will not work in VR. Use ARWTHVRPawn::SetScale(float) instead.")
+			;
 		}
-		UE_LOGFMT(Toolkit, Warning,
-		          "ARWTHVRPawn: Do not adjust the scale of the pawn directly. This will not work in VR. Use ARWTHVRPawn::SetScale(float) instead.")
-		;
 	});
 }
 
@@ -84,9 +79,17 @@ void ARWTHVRPawn::Tick(float DeltaSeconds)
  */
 void ARWTHVRPawn::SetScale(float NewScale)
 {
-	FVector NewScaleVector = FVector(NewScale, NewScale, NewScale);
-	GetWorldSettings()->WorldToMeters = InitialWorldToMeters * NewScale;
-	SetActorScale3D(NewScaleVector);
+	FVector OldScale = GetActorScale();
+	UniformScale = NewScale;
+	FVector NewScaleVector = FVector(UniformScale, UniformScale, UniformScale);
+	GetWorldSettings()->WorldToMeters = InitialWorldToMeters * UniformScale;
+	SetActorRelativeScale3D(NewScaleVector);
+	OnScaleChanged.Broadcast(OldScale, NewScale);
+}
+
+float ARWTHVRPawn::GetScale()
+{
+	return UniformScale;
 }
 
 /*
diff --git a/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h b/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h
index eaf24745..bad99461 100644
--- a/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h
+++ b/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h
@@ -14,6 +14,8 @@ class UCameraComponent;
 class UMotionControllerComponent;
 struct FLiveLinkTransformStaticData;
 
+DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnScaleChangedDelegate, FVector, OldScale, float, NewUniformScale);
+
 /**
  * Pawn implementation with additional VR functionality, can be used in the Cave, with an HMD and on desktop.
  */
@@ -30,10 +32,16 @@ public:
 	virtual void Tick(float DeltaSeconds) override;
 
 	virtual void NotifyControllerChanged() override;
-
+	
 	UFUNCTION(BlueprintCallable)
 	void SetScale(float NewScale);
-	
+
+	UFUNCTION(BlueprintCallable)
+	float GetScale();
+
+	UPROPERTY(BlueprintAssignable)
+	FOnScaleChangedDelegate OnScaleChanged;
+
 	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Pawn|Input")
 	TArray<UInputMappingContext*> InputMappingContexts;
 
@@ -115,4 +123,5 @@ protected:
 private:
 	UInputComponent* ActivePlayerInputComponent;
 	float InitialWorldToMeters;
+	float UniformScale;
 };
-- 
GitLab


From e3e150ba6f616a0521ec7863450b392438dd995e Mon Sep 17 00:00:00 2001
From: Kris Helwig <helwig@vr.rwth-aachen.de>
Date: Mon, 17 Mar 2025 15:20:17 +0100
Subject: [PATCH 03/14] fix(movement): Scales the CollisionHandlingMovement
 capsule to counteract pawn scaling

---
 .../Private/Pawn/Navigation/CollisionHandlingMovement.cpp     | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/Source/RWTHVRToolkit/Private/Pawn/Navigation/CollisionHandlingMovement.cpp b/Source/RWTHVRToolkit/Private/Pawn/Navigation/CollisionHandlingMovement.cpp
index 62bfa460..0effb261 100644
--- a/Source/RWTHVRToolkit/Private/Pawn/Navigation/CollisionHandlingMovement.cpp
+++ b/Source/RWTHVRToolkit/Private/Pawn/Navigation/CollisionHandlingMovement.cpp
@@ -3,6 +3,7 @@
 #include "Kismet/KismetSystemLibrary.h"
 #include "Logging/StructuredLog.h"
 #include "Utility/RWTHVRUtilities.h"
+#include "DrawDebugHelpers.h"
 
 UCollisionHandlingMovement::UCollisionHandlingMovement(const FObjectInitializer& ObjectInitializer) :
 	Super(ObjectInitializer)
@@ -189,6 +190,9 @@ void UCollisionHandlingMovement::SetCapsuleColliderToUserSize() const
 	}
 
 	CapsuleColliderComponent->SetWorldRotation(FRotator::ZeroRotator);
+
+	// Counteract Pawn Scaling
+	CapsuleColliderComponent->SetWorldScale3D(FVector::One());
 }
 
 void UCollisionHandlingMovement::CheckAndRevertCollisionSinceLastTick()
-- 
GitLab


From 5fff5b7ee76a2026588cec9439674a21de7840e3 Mon Sep 17 00:00:00 2001
From: Kris Helwig <helwig@vr.rwth-aachen.de>
Date: Mon, 17 Mar 2025 15:42:51 +0100
Subject: [PATCH 04/14] style(pawn): fixes clang format in RWTHVRPawn.cpp and
 RWTHVRPawn.h

---
 .../RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp | 46 +++++++++----------
 Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h |  2 +-
 2 files changed, 23 insertions(+), 25 deletions(-)

diff --git a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
index 7e32a90d..f773fa9f 100644
--- a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
+++ b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
@@ -43,16 +43,17 @@ ARWTHVRPawn::ARWTHVRPawn(const FObjectInitializer& ObjectInitializer) : Super(Ob
 	LeftHand->SetupAttachment(RootComponent);
 
 	UniformScale = GetActorScale3D().X;
-	GetRootComponent()->TransformUpdated.AddLambda([this](USceneComponent*, EUpdateTransformFlags, ETeleportType)
-	{
-		FVector CurrentScale = this->GetActorScale3D();
-		if (CurrentScale.X != UniformScale || CurrentScale.Y != UniformScale || CurrentScale.Z != UniformScale)
+	GetRootComponent()->TransformUpdated.AddLambda(
+		[this](USceneComponent*, EUpdateTransformFlags, ETeleportType)
 		{
-			UE_LOGFMT(Toolkit, Warning,
-			          "ARWTHVRPawn: Do not adjust the scale of the pawn directly. This will not work in VR. Use ARWTHVRPawn::SetScale(float) instead.")
-			;
-		}
-	});
+			FVector CurrentScale = this->GetActorScale3D();
+			if (CurrentScale.X != UniformScale || CurrentScale.Y != UniformScale || CurrentScale.Z != UniformScale)
+			{
+				UE_LOGFMT(Toolkit, Warning,
+						  "ARWTHVRPawn: Do not adjust the scale of the pawn directly. This will not work in VR. Use "
+						  "ARWTHVRPawn::SetScale(float) instead.");
+			}
+		});
 }
 
 void ARWTHVRPawn::BeginPlay()
@@ -87,10 +88,7 @@ void ARWTHVRPawn::SetScale(float NewScale)
 	OnScaleChanged.Broadcast(OldScale, NewScale);
 }
 
-float ARWTHVRPawn::GetScale()
-{
-	return UniformScale;
-}
+float ARWTHVRPawn::GetScale() {	return UniformScale; }
 
 /*
  * The alternative would be to do this only on the server on possess and check for player state/type,
@@ -107,7 +105,7 @@ void ARWTHVRPawn::NotifyControllerChanged()
 	if (HasAuthority())
 	{
 		UE_LOG(Toolkit, Display,
-		       TEXT("ARWTHVRPawn: Player Controller has changed, trying to change Cluster attachment if possible..."));
+			   TEXT("ARWTHVRPawn: Player Controller has changed, trying to change Cluster attachment if possible..."));
 		if (const ARWTHVRPlayerState* State = GetPlayerState<ARWTHVRPlayerState>())
 		{
 			const EPlayerType Type = State->GetPlayerType();
@@ -137,7 +135,7 @@ void ARWTHVRPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponen
 	}
 
 	UE_LOGFMT(Toolkit, Display, "SetupPlayerInputComponent: Player Controller is valid, setting up input for {Pawn}",
-	          GetName());
+			  GetName());
 
 
 	// Set the control rotation of the PC to zero again. There is a small period of 2 frames where, when the pawn gets
@@ -209,7 +207,7 @@ void ARWTHVRPawn::AddInputMappingContext(const APlayerController* PC, const UInp
 			else
 			{
 				UE_LOGFMT(Toolkit, Warning,
-				          "ARWTHVRPawn::AddInputMappingContext: UEnhancedInputLocalPlayerSubsystem is nullptr!");
+						  "ARWTHVRPawn::AddInputMappingContext: UEnhancedInputLocalPlayerSubsystem is nullptr!");
 			}
 		}
 		else
@@ -237,7 +235,7 @@ void ARWTHVRPawn::EvaluateLivelink() const
 			IModularFeatures::Get().GetModularFeature<ILiveLinkClient>(ILiveLinkClient::ModularFeatureName);
 		FLiveLinkSubjectFrameData SubjectData;
 		const bool bHasValidData = LiveLinkClient.EvaluateFrame_AnyThread(HeadSubjectRepresentation.Subject,
-		                                                                  HeadSubjectRepresentation.Role, SubjectData);
+																		  HeadSubjectRepresentation.Role, SubjectData);
 
 		if (!bHasValidData)
 		{
@@ -314,12 +312,12 @@ void ARWTHVRPawn::AttachClustertoPawn()
 		bool bAttached = ClusterActor->AttachToComponent(GetRootComponent(), AttachmentRules);
 		// State->GetCorrespondingClusterActor()->OnAttached();
 		UE_LOGFMT(Toolkit, Display,
-		          "ARWTHVRPawn: Attaching corresponding cluster actor to our pawn returned: {Attached}", bAttached);
+				  "ARWTHVRPawn: Attaching corresponding cluster actor to our pawn returned: {Attached}", bAttached);
 	}
 	else
 	{
 		UE_LOGFMT(Toolkit, Error,
-		          "ARWTHVRPawn::AttachClustertoPawn: No ARWTHVRPlayerState set! This won't work on the Cave.");
+				  "ARWTHVRPawn::AttachClustertoPawn: No ARWTHVRPlayerState set! This won't work on the Cave.");
 	}
 
 	if (HasAuthority()) // Should always be the case here, but double check
@@ -357,19 +355,19 @@ void ARWTHVRPawn::SetCameraOffset() const
 }
 
 void ARWTHVRPawn::ApplyLiveLinkTransform(const FTransform& Transform,
-                                         const FLiveLinkTransformStaticData& StaticData) const
+										 const FLiveLinkTransformStaticData& StaticData) const
 {
 	if (StaticData.bIsLocationSupported)
 	{
 		if (bWorldTransform)
 		{
 			HeadCameraComponent->SetWorldLocation(Transform.GetLocation(), false, nullptr,
-			                                      ETeleportType::TeleportPhysics);
+												  ETeleportType::TeleportPhysics);
 		}
 		else
 		{
 			HeadCameraComponent->SetRelativeLocation(Transform.GetLocation(), false, nullptr,
-			                                         ETeleportType::TeleportPhysics);
+													 ETeleportType::TeleportPhysics);
 		}
 	}
 
@@ -378,12 +376,12 @@ void ARWTHVRPawn::ApplyLiveLinkTransform(const FTransform& Transform,
 		if (bWorldTransform)
 		{
 			HeadCameraComponent->SetWorldRotation(Transform.GetRotation(), false, nullptr,
-			                                      ETeleportType::TeleportPhysics);
+												  ETeleportType::TeleportPhysics);
 		}
 		else
 		{
 			HeadCameraComponent->SetRelativeRotation(Transform.GetRotation(), false, nullptr,
-			                                         ETeleportType::TeleportPhysics);
+													 ETeleportType::TeleportPhysics);
 		}
 	}
 
diff --git a/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h b/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h
index bad99461..40eb174f 100644
--- a/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h
+++ b/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h
@@ -32,7 +32,7 @@ public:
 	virtual void Tick(float DeltaSeconds) override;
 
 	virtual void NotifyControllerChanged() override;
-	
+
 	UFUNCTION(BlueprintCallable)
 	void SetScale(float NewScale);
 
-- 
GitLab


From 4e8d0770cd8145ca9e682e630798dfde0b36f9e1 Mon Sep 17 00:00:00 2001
From: Kris Helwig <helwig@vr.rwth-aachen.de>
Date: Mon, 17 Mar 2025 15:45:30 +0100
Subject: [PATCH 05/14] style(pawn): fixes clang format in RWTHVRPawn.cpp and
 RWTHVRPawn.h again

---
 Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
index f773fa9f..dc9c9022 100644
--- a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
+++ b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
@@ -88,7 +88,7 @@ void ARWTHVRPawn::SetScale(float NewScale)
 	OnScaleChanged.Broadcast(OldScale, NewScale);
 }
 
-float ARWTHVRPawn::GetScale() {	return UniformScale; }
+float ARWTHVRPawn::GetScale() { return UniformScale; }
 
 /*
  * The alternative would be to do this only on the server on possess and check for player state/type,
-- 
GitLab


From b53030de656fc241154cd842753d02b7f2f16ff2 Mon Sep 17 00:00:00 2001
From: David Gilbert <gilbert@vr.rwth-aachen.de>
Date: Thu, 20 Mar 2025 15:50:31 +0100
Subject: [PATCH 06/14] style(movement): removes unnecessary include

---
 .../Private/Pawn/Navigation/CollisionHandlingMovement.cpp        | 1 -
 1 file changed, 1 deletion(-)

diff --git a/Source/RWTHVRToolkit/Private/Pawn/Navigation/CollisionHandlingMovement.cpp b/Source/RWTHVRToolkit/Private/Pawn/Navigation/CollisionHandlingMovement.cpp
index 0effb261..912fe0c1 100644
--- a/Source/RWTHVRToolkit/Private/Pawn/Navigation/CollisionHandlingMovement.cpp
+++ b/Source/RWTHVRToolkit/Private/Pawn/Navigation/CollisionHandlingMovement.cpp
@@ -3,7 +3,6 @@
 #include "Kismet/KismetSystemLibrary.h"
 #include "Logging/StructuredLog.h"
 #include "Utility/RWTHVRUtilities.h"
-#include "DrawDebugHelpers.h"
 
 UCollisionHandlingMovement::UCollisionHandlingMovement(const FObjectInitializer& ObjectInitializer) :
 	Super(ObjectInitializer)
-- 
GitLab


From 8dd3010c03bbd82aa1d6df3e4c40d6d6a8469db7 Mon Sep 17 00:00:00 2001
From: Kris Helwig <helwig@vr.rwth-aachen.de>
Date: Tue, 8 Apr 2025 13:03:11 +0200
Subject: [PATCH 07/14] refactor(pawn): changes from a delegate approach to an
 interface approach

---
 Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
index dc9c9022..31874d9f 100644
--- a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
+++ b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
@@ -18,6 +18,10 @@
 #include "Utility/RWTHVRUtilities.h"
 
 #if PLATFORM_SUPPORTS_CLUSTER
+#include "DisplayClusterRootActor.h"
+#include "ScalableConfigInterface.h"
+#include "IDisplayCluster.h"
+#include "Game/IDisplayClusterGameManager.h"
 #include "Components/DisplayClusterSceneComponentSyncParent.h"
 #endif
 
@@ -85,7 +89,16 @@ void ARWTHVRPawn::SetScale(float NewScale)
 	FVector NewScaleVector = FVector(UniformScale, UniformScale, UniformScale);
 	GetWorldSettings()->WorldToMeters = InitialWorldToMeters * UniformScale;
 	SetActorRelativeScale3D(NewScaleVector);
-	OnScaleChanged.Broadcast(OldScale, NewScale);
+	
+#if PLATFORM_SUPPORTS_CLUSTER
+	const ARWTHVRPlayerState* State = GetPlayerState<ARWTHVRPlayerState>();
+	if (URWTHVRUtilities::IsHeadMountedMode() && State && State->GetCorrespondingClusterActor())
+	{
+		const auto ClusterRootActor = IDisplayCluster::Get().GetGameMgr()->GetRootActor();
+		IScalableConfigInterface* ConfigInterface = Cast<IScalableConfigInterface>(ClusterRootActor);
+		ConfigInterface->OnScaleChanged(NewScale);
+	}
+#endif
 }
 
 float ARWTHVRPawn::GetScale() { return UniformScale; }
-- 
GitLab


From 043d07fa7933e1873f87c21b9eb91d2dc8cba97f Mon Sep 17 00:00:00 2001
From: Kris Tabea Helwig <helwig@vr.rwth-aachen.de>
Date: Tue, 8 Apr 2025 13:50:07 +0200
Subject: [PATCH 08/14] Sets correct branch

---
 .gitlab-ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 93d693e4..dce45555 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -83,7 +83,7 @@ Generate_Project:
         RUN_SETUP: "false"
         GEN_DEPENDENCIES: "(
             [master@UnrealDTrackPlugin]='https://github.com/VRGroupRWTH/UnrealDTrackPlugin.git'
-            [dev/5.4@RWTHVRCluster]='https://git-ce.rwth-aachen.de/vr-vis/VR-Group/unreal-development/plugins/rwth-vr-cluster-plugin.git'
+            [feature/scaling@RWTHVRCluster]='https://git-ce.rwth-aachen.de/vr-vis/VR-Group/unreal-development/plugins/rwth-vr-cluster-plugin.git'
             )"
 
 Generate_Project_Without_Cluster:
-- 
GitLab


From a5eeaa493ded672a665d49c74918845afd131fbc Mon Sep 17 00:00:00 2001
From: Kris Helwig <helwig@vr.rwth-aachen.de>
Date: Tue, 8 Apr 2025 16:29:31 +0200
Subject: [PATCH 09/14] clang

---
 Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
index 31874d9f..b79168b6 100644
--- a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
+++ b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
@@ -89,7 +89,7 @@ void ARWTHVRPawn::SetScale(float NewScale)
 	FVector NewScaleVector = FVector(UniformScale, UniformScale, UniformScale);
 	GetWorldSettings()->WorldToMeters = InitialWorldToMeters * UniformScale;
 	SetActorRelativeScale3D(NewScaleVector);
-	
+
 #if PLATFORM_SUPPORTS_CLUSTER
 	const ARWTHVRPlayerState* State = GetPlayerState<ARWTHVRPlayerState>();
 	if (URWTHVRUtilities::IsHeadMountedMode() && State && State->GetCorrespondingClusterActor())
-- 
GitLab


From 193a009aab78ea8af1f4415ffe5d022b546b14ec Mon Sep 17 00:00:00 2001
From: Kris Helwig <helwig@vr.rwth-aachen.de>
Date: Fri, 11 Apr 2025 09:45:06 +0200
Subject: [PATCH 10/14] Fix(Pawn): Fixed config interface casting

---
 .../RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp | 22 ++++++++++++++-----
 1 file changed, 17 insertions(+), 5 deletions(-)

diff --git a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
index b79168b6..8f0ecb46 100644
--- a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
+++ b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
@@ -84,7 +84,6 @@ void ARWTHVRPawn::Tick(float DeltaSeconds)
  */
 void ARWTHVRPawn::SetScale(float NewScale)
 {
-	FVector OldScale = GetActorScale();
 	UniformScale = NewScale;
 	FVector NewScaleVector = FVector(UniformScale, UniformScale, UniformScale);
 	GetWorldSettings()->WorldToMeters = InitialWorldToMeters * UniformScale;
@@ -92,11 +91,24 @@ void ARWTHVRPawn::SetScale(float NewScale)
 
 #if PLATFORM_SUPPORTS_CLUSTER
 	const ARWTHVRPlayerState* State = GetPlayerState<ARWTHVRPlayerState>();
-	if (URWTHVRUtilities::IsHeadMountedMode() && State && State->GetCorrespondingClusterActor())
+	if (URWTHVRUtilities::IsRoomMountedMode() && State && State->GetCorrespondingClusterActor())
 	{
-		const auto ClusterRootActor = IDisplayCluster::Get().GetGameMgr()->GetRootActor();
-		IScalableConfigInterface* ConfigInterface = Cast<IScalableConfigInterface>(ClusterRootActor);
-		ConfigInterface->OnScaleChanged(NewScale);
+		if (const auto GameMgr = IDisplayCluster::Get().GetGameMgr())
+		{
+			if (const auto ClusterRootActor = GameMgr->GetRootActor())
+			{
+				if (ClusterRootActor->Implements<UScalableConfigInterface>())
+				{
+					IScalableConfigInterface::Execute_OnScaleChanged(ClusterRootActor, NewScale);
+				}
+				else
+				{
+					UE_LOGFMT(Toolkit, Warning,
+							  "The ClusterRootActor {0} does not implement the ScalableConfigInterface. Scaling the Pawn on the cluster will lead to unintended behavior.",
+							  ClusterRootActor->GetName());
+				}
+			}
+		}
 	}
 #endif
 }
-- 
GitLab


From bbed2d2c4c47e59ea760d42cb7456623bc162c57 Mon Sep 17 00:00:00 2001
From: Kris Helwig <helwig@vr.rwth-aachen.de>
Date: Fri, 11 Apr 2025 09:48:14 +0200
Subject: [PATCH 11/14] clang(Pawn)

---
 Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
index 8f0ecb46..9becddbd 100644
--- a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
+++ b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
@@ -104,7 +104,8 @@ void ARWTHVRPawn::SetScale(float NewScale)
 				else
 				{
 					UE_LOGFMT(Toolkit, Warning,
-							  "The ClusterRootActor {0} does not implement the ScalableConfigInterface. Scaling the Pawn on the cluster will lead to unintended behavior.",
+							  "The ClusterRootActor {0} does not implement the ScalableConfigInterface. Scaling the "
+							  "Pawn on the cluster will lead to unintended behavior.",
 							  ClusterRootActor->GetName());
 				}
 			}
-- 
GitLab


From 01a12211c2261f05b4b1af206b390cc380c91459 Mon Sep 17 00:00:00 2001
From: Kris Helwig <helwig@vr.rwth-aachen.de>
Date: Thu, 12 Jun 2025 09:36:16 +0200
Subject: [PATCH 12/14] docs(pawn): updates comment for clarity

---
 Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
index 9becddbd..3298a60c 100644
--- a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
+++ b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
@@ -79,7 +79,7 @@ void ARWTHVRPawn::Tick(float DeltaSeconds)
 }
 
 /*
- *	Scales the Pawn while also adjusting the WorldToMeters ratio to adjust for pupillary distance.
+ *	Scales the Pawn while also adjusting the WorldToMeters ratio for size relative movement speed.
  *	Only supports uniform scaling.
  */
 void ARWTHVRPawn::SetScale(float NewScale)
-- 
GitLab


From 794e5ac7d495d76174e2a22958a9495b76cda537 Mon Sep 17 00:00:00 2001
From: Kris Helwig <helwig@vr.rwth-aachen.de>
Date: Thu, 12 Jun 2025 10:48:22 +0200
Subject: [PATCH 13/14] fix(pawn): removes worldtometers scaling

---
 Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp | 10 ++--------
 Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h    |  1 -
 2 files changed, 2 insertions(+), 9 deletions(-)

diff --git a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
index 3298a60c..f8d78c33 100644
--- a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
+++ b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
@@ -60,11 +60,7 @@ ARWTHVRPawn::ARWTHVRPawn(const FObjectInitializer& ObjectInitializer) : Super(Ob
 		});
 }
 
-void ARWTHVRPawn::BeginPlay()
-{
-	Super::BeginPlay();
-	InitialWorldToMeters = GetWorldSettings()->WorldToMeters;
-}
+void ARWTHVRPawn::BeginPlay() { Super::BeginPlay(); }
 
 void ARWTHVRPawn::Tick(float DeltaSeconds)
 {
@@ -79,14 +75,12 @@ void ARWTHVRPawn::Tick(float DeltaSeconds)
 }
 
 /*
- *	Scales the Pawn while also adjusting the WorldToMeters ratio for size relative movement speed.
- *	Only supports uniform scaling.
+ *	Scales the Pawn. Only supports uniform scaling.
  */
 void ARWTHVRPawn::SetScale(float NewScale)
 {
 	UniformScale = NewScale;
 	FVector NewScaleVector = FVector(UniformScale, UniformScale, UniformScale);
-	GetWorldSettings()->WorldToMeters = InitialWorldToMeters * UniformScale;
 	SetActorRelativeScale3D(NewScaleVector);
 
 #if PLATFORM_SUPPORTS_CLUSTER
diff --git a/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h b/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h
index 40eb174f..dcd506b1 100644
--- a/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h
+++ b/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h
@@ -122,6 +122,5 @@ protected:
 
 private:
 	UInputComponent* ActivePlayerInputComponent;
-	float InitialWorldToMeters;
 	float UniformScale;
 };
-- 
GitLab


From 594b471789873da5c9abeac3baec641cdfe8a416 Mon Sep 17 00:00:00 2001
From: Kris Tabea Helwig <helwig@vr.rwth-aachen.de>
Date: Thu, 12 Jun 2025 13:16:01 +0200
Subject: [PATCH 14/14] Edit .gitlab-ci.yml

---
 .gitlab-ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index dce45555..93d693e4 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -83,7 +83,7 @@ Generate_Project:
         RUN_SETUP: "false"
         GEN_DEPENDENCIES: "(
             [master@UnrealDTrackPlugin]='https://github.com/VRGroupRWTH/UnrealDTrackPlugin.git'
-            [feature/scaling@RWTHVRCluster]='https://git-ce.rwth-aachen.de/vr-vis/VR-Group/unreal-development/plugins/rwth-vr-cluster-plugin.git'
+            [dev/5.4@RWTHVRCluster]='https://git-ce.rwth-aachen.de/vr-vis/VR-Group/unreal-development/plugins/rwth-vr-cluster-plugin.git'
             )"
 
 Generate_Project_Without_Cluster:
-- 
GitLab