Skip to content
Snippets Groups Projects
Select Git revision
  • dev/5.5
  • 5.5
  • dev/5.4
  • dev/5.3_downgrade
  • feature/experimenttime_hack
  • 5.4 default protected
  • 5.3 protected
  • _IntenSelect5.3
  • IntenSelect5.3
  • 4.27 protected
  • 4.26 protected
  • 5.0 protected
  • 4.22 protected
  • 4.21 protected
  • UE5.4-2024.1-rc1
  • UE5.3-2023.1-rc3
  • UE5.3-2023.1-rc2
  • UE5.3-2023.1-rc
18 results

RWTHVRPawn.cpp

Blame
  • RWTHVRPawn.cpp 11.38 KiB
    // Fill out your copyright notice in the Description page of Project Settings.
    
    #include "Pawn/RWTHVRPawn.h"
    
    #include "EnhancedInputSubsystems.h"
    #include "Engine/LocalPlayer.h"
    #include "GameFramework/PlayerController.h"
    #include "ILiveLinkClient.h"
    #include "Core/RWTHVRPlayerState.h"
    #include "Kismet/GameplayStatics.h"
    #include "Logging/StructuredLog.h"
    #include "Pawn/InputExtensionInterface.h"
    #include "Pawn/Navigation/CollisionHandlingMovement.h"
    #include "Pawn/ReplicatedCameraComponent.h"
    #include "Pawn/ReplicatedMotionControllerComponent.h"
    #include "Roles/LiveLinkTransformTypes.h"
    #include "Utility/RWTHVRUtilities.h"
    
    ARWTHVRPawn::ARWTHVRPawn(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
    {
    	BaseEyeHeight = 160.0f;
    
    	SetRootComponent(CreateDefaultSubobject<USceneComponent>(TEXT("Origin")));
    
    	HeadCameraComponent = CreateDefaultSubobject<UReplicatedCameraComponent>(TEXT("Camera"));
    	HeadCameraComponent->SetupAttachment(RootComponent);
    	HeadCameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, BaseEyeHeight));
    	// so it is rendered correctly in editor
    
    	CollisionHandlingMovement = CreateDefaultSubobject<UCollisionHandlingMovement>(TEXT("Collision Handling Movement"));
    	CollisionHandlingMovement->SetUpdatedComponent(RootComponent);
    	CollisionHandlingMovement->SetHeadComponent(HeadCameraComponent);
    
    	RightHand = CreateDefaultSubobject<UReplicatedMotionControllerComponent>(TEXT("Right Hand MCC"));
    	RightHand->SetupAttachment(RootComponent);
    
    	LeftHand = CreateDefaultSubobject<UReplicatedMotionControllerComponent>(TEXT("Left Hand MCC"));
    	LeftHand->SetupAttachment(RootComponent);
    
    	// Add an nDisplay Parent Sync Component. It syncs the parent's transform from master to clients.
    	// This is required because for collision based movement, it can happen that the physics engine
    	// for some reason acts different on the nodes, therefore leading to a potential desync when
    	// e.g. colliding with an object while moving.
    	SyncComponent =
    		CreateDefaultSubobject<UDisplayClusterSceneComponentSyncParent>(TEXT("Parent Display Cluster Sync Component"));
    	SyncComponent->SetupAttachment(RootComponent);
    }
    
    void ARWTHVRPawn::Tick(float DeltaSeconds)
    {
    	Super::Tick(DeltaSeconds);
    
    	if (URWTHVRUtilities::IsDesktopMode() && IsLocallyControlled())
    	{
    		SetCameraOffset();
    		UpdateRightHandForDesktopInteraction();
    	}
    	EvaluateLivelink();
    }
    
    /*
     * 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.
     */
    // This pawn's controller has changed! This is called on both server and owning client. If we are the owning client
    // and the master, request that the DCRA is attached to us.
    void ARWTHVRPawn::NotifyControllerChanged()
    {
    	Super::NotifyControllerChanged();
    
    	// Only do this for all local controlled pawns
    	if (IsLocallyControlled())
    	{
    		// Only do this for the primary node or when we're running in standalone
    		if (URWTHVRUtilities::IsRoomMountedMode() &&
    			(URWTHVRUtilities::IsPrimaryNode() || GetNetMode() == NM_Standalone))
    		{
    			// If we are also the authority (standalone or listen server), directly attach it to us.
    			// If we are not (client), ask the server to do it.
    			if (HasAuthority())
    			{
    				AttachDCRAtoPawn();
    			}
    			else
    			{
    				ServerAttachDCRAtoPawnRpc();
    			}
    		}
    	}
    }
    
    void ARWTHVRPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
    {
    	Super::SetupPlayerInputComponent(PlayerInputComponent);
    
    	APlayerController* PlayerController = Cast<APlayerController>(GetController());
    	if (!PlayerController)
    	{
    		UE_LOG(Toolkit, Warning, TEXT("SetupPlayerInputComponent: Player Controller is invalid"));
    		return;
    	}
    
    	// Set the control rotation of the PC to zero again. There is a small period of 2 frames where, when the pawn gets
    	// possessed, the PC takes on the rotation of the VR Headset ONLY WHEN SPAWNING ON A CLIENT. Reset the rotation here
    	// such that bUseControllerRotationYaw=true does not pass the wrong yaw value to the pawn initially. There is
    	// probably a checkbox or way of spawning that prevents that in a better way that this, change if found.
    	PlayerController->SetControlRotation(FRotator::ZeroRotator);
    
    	SetupMotionControllerSources();
    
    	// Should not do this here but on connection or on possess I think.
    	if (ARWTHVRPlayerState* State = GetPlayerState<ARWTHVRPlayerState>())
    	{
    		// Might not be properly synced yet?
    		const EPlayerType Type = State->GetPlayerType();
    
    		// Don't do anything with the type if it's been set to clustertype or anything.
    		const bool bClusterType = Type == EPlayerType::nDisplayPrimary || Type == EPlayerType::nDisplaySecondary;
    
    		if (!bClusterType && URWTHVRUtilities::IsHeadMountedMode())
    		{
    			// Could be too early to call this RPC...
    			State->RequestSetPlayerType(Type);
    		}
    	}
    
    	if (URWTHVRUtilities::IsDesktopMode())
    	{
    		PlayerController->bShowMouseCursor = true;
    		PlayerController->bEnableClickEvents = true;
    		PlayerController->bEnableMouseOverEvents = true;
    	}
    
    	// Set up mappings on input extension components, need to do this nicely
    
    	for (UActorComponent* Comp : GetComponentsByInterface(UInputExtensionInterface::StaticClass()))
    	{
    		Cast<IInputExtensionInterface>(Comp)->SetupPlayerInput(PlayerInputComponent);
    	}
    
    	// bind the current mapping contexts
    	for (const auto& Mapping : InputMappingContexts)
    	{
    		AddInputMappingContext(PlayerController, Mapping);
    	}
    }
    
    void ARWTHVRPawn::AddInputMappingContext(const APlayerController* PC, const UInputMappingContext* Context) const
    {
    	if (Context)
    	{
    		if (const ULocalPlayer* LP = PC->GetLocalPlayer())
    		{
    			if (UEnhancedInputLocalPlayerSubsystem* InputSub = LP->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
    			{
    				InputSub->AddMappingContext(Context, 0);
    			}
    			else
    			{
    				UE_LOGFMT(Toolkit, Warning,
    						  "ARWTHVRPawn::AddInputMappingContext: UEnhancedInputLocalPlayerSubsystem is nullptr!");
    			}
    		}
    		else
    		{
    			UE_LOGFMT(Toolkit, Warning, "ARWTHVRPawn::AddInputMappingContext: LocalPlayer is nullptr!");
    		}
    	}
    	else
    	{
    		UE_LOGFMT(Toolkit, Warning, "ARWTHVRPawn::AddInputMappingContext: Context is nullptr!");
    	}
    }
    
    void ARWTHVRPawn::EvaluateLivelink() const
    {
    	if (URWTHVRUtilities::IsRoomMountedMode() && IsLocallyControlled())
    	{
    		if (bDisableLiveLink || HeadSubjectRepresentation.Subject.IsNone() || HeadSubjectRepresentation.Role == nullptr)
    		{
    			return;
    		}
    
    		// Get the LiveLink interface and evaluate the current existing frame data for the given Subject and Role.
    		ILiveLinkClient& LiveLinkClient =
    			IModularFeatures::Get().GetModularFeature<ILiveLinkClient>(ILiveLinkClient::ModularFeatureName);
    		FLiveLinkSubjectFrameData SubjectData;
    		const bool bHasValidData = LiveLinkClient.EvaluateFrame_AnyThread(HeadSubjectRepresentation.Subject,
    																		  HeadSubjectRepresentation.Role, SubjectData);
    
    		if (!bHasValidData)
    		{
    			return;
    		}
    
    		// Assume we are using a Transform Role to track the components! This is a slightly dangerous assumption, and
    		// could be further improved.
    		const FLiveLinkTransformStaticData* StaticData = SubjectData.StaticData.Cast<FLiveLinkTransformStaticData>();
    		const FLiveLinkTransformFrameData* FrameData = SubjectData.FrameData.Cast<FLiveLinkTransformFrameData>();
    
    		if (StaticData && FrameData)
    		{
    			// Finally, apply the transform to this component according to the static data.
    			ApplyLiveLinkTransform(FrameData->Transform, *StaticData);
    		}
    	}
    }
    
    void ARWTHVRPawn::UpdateRightHandForDesktopInteraction() const
    {
    	if (const APlayerController* PC = Cast<APlayerController>(GetController()))
    	{
    		FVector MouseLocation, MouseDirection;
    		PC->DeprojectMousePositionToWorld(MouseLocation, MouseDirection);
    		const FRotator HandOrientation = MouseDirection.ToOrientationRotator();
    		if (bMoveRightHandWithMouse)
    		{
    			RightHand->SetWorldRotation(HandOrientation);
    			RightHand->SetRelativeLocation(HeadCameraComponent->GetRelativeLocation());
    		}
    		if (bMoveLeftHandWithMouse)
    		{
    			LeftHand->SetWorldRotation(HandOrientation);
    			LeftHand->SetRelativeLocation(HeadCameraComponent->GetRelativeLocation());
    		}
    	}
    }
    
    // Todo rewrite this in some other way or attach it differently, this is horrible
    // Executed on the server only: Finds and attaches the CaveSetup Actor, which contains the DCRA to the Pawn.
    // It is only executed on the server because attachments are synced to all clients, but not from client to server.
    void ARWTHVRPawn::AttachDCRAtoPawn()
    {
    	if (!CaveSetupActorClass || !CaveSetupActorClass->IsValidLowLevelFast())
    	{
    		UE_LOGFMT(Toolkit, Warning, "No CaveSetup Actor class set in pawn!");
    		return;
    	}
    
    	// Find our CaveSetup actor
    	TArray<AActor*> FoundActors;
    	UGameplayStatics::GetAllActorsOfClass(GetWorld(), CaveSetupActorClass, FoundActors);
    
    	if (!FoundActors.IsEmpty())
    	{
    		const auto CaveSetupActor = FoundActors[0];
    		FAttachmentTransformRules AttachmentRules = FAttachmentTransformRules::SnapToTargetNotIncludingScale;
    		AttachmentRules.RotationRule = EAttachmentRule::KeepWorld;
    		CaveSetupActor->AttachToActor(this, AttachmentRules);
    		UE_LOGFMT(Toolkit, Display, "VirtualRealityPawn: Attaching CaveSetup to our pawn!");
    	}
    	else
    	{
    		UE_LOGFMT(Toolkit, Warning,
    				  "No CaveSetup Actor found which can be attached to the Pawn! This won't work on the Cave.");
    	}
    }
    
    void ARWTHVRPawn::SetupMotionControllerSources()
    {
    	// Setup Motion Controllers
    
    	FName MotionControllerSourceLeft = EName::None;
    	FName MotionControllerSourceRight = EName::None;
    	if (URWTHVRUtilities::IsHeadMountedMode())
    	{
    		MotionControllerSourceLeft = FName("Left");
    		MotionControllerSourceRight = FName("Right");
    	}
    	if (URWTHVRUtilities::IsRoomMountedMode())
    	{
    		MotionControllerSourceLeft = LeftSubjectRepresentation.Subject;
    		MotionControllerSourceRight = RightSubjectRepresentation.Subject;
    	}
    	LeftHand->SetTrackingMotionSource(MotionControllerSourceLeft);
    	RightHand->SetTrackingMotionSource(MotionControllerSourceRight);
    }
    
    // Requests the server to perform the attachment, as only the server can sync this to all the other clients.
    void ARWTHVRPawn::ServerAttachDCRAtoPawnRpc_Implementation()
    {
    	// We're on the server here - attach the actor to the pawn.
    	AttachDCRAtoPawn();
    }
    
    void ARWTHVRPawn::SetCameraOffset() const
    {
    	// this also incorporates the BaseEyeHeight, if set as static offset,
    	// rotations are still around the center of the pawn (on the floor), so pitch rotations look weird
    	FVector Location;
    	FRotator Rotation;
    	GetActorEyesViewPoint(Location, Rotation);
    	HeadCameraComponent->SetWorldLocationAndRotation(Location, Rotation);
    }
    
    void ARWTHVRPawn::ApplyLiveLinkTransform(const FTransform& Transform,
    										 const FLiveLinkTransformStaticData& StaticData) const
    {
    	if (StaticData.bIsLocationSupported)
    	{
    		if (bWorldTransform)
    		{
    			HeadCameraComponent->SetWorldLocation(Transform.GetLocation(), false, nullptr,
    												  ETeleportType::TeleportPhysics);
    		}
    		else
    		{
    			HeadCameraComponent->SetRelativeLocation(Transform.GetLocation(), false, nullptr,
    													 ETeleportType::TeleportPhysics);
    		}
    	}
    
    	if (StaticData.bIsRotationSupported)
    	{
    		if (bWorldTransform)
    		{
    			HeadCameraComponent->SetWorldRotation(Transform.GetRotation(), false, nullptr,
    												  ETeleportType::TeleportPhysics);
    		}
    		else
    		{
    			HeadCameraComponent->SetRelativeRotation(Transform.GetRotation(), false, nullptr,
    													 ETeleportType::TeleportPhysics);
    		}
    	}
    
    	if (StaticData.bIsScaleSupported)
    	{
    		if (bWorldTransform)
    		{
    			HeadCameraComponent->SetWorldScale3D(Transform.GetScale3D());
    		}
    		else
    		{
    			HeadCameraComponent->SetRelativeScale3D(Transform.GetScale3D());
    		}
    	}
    }