// 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 AddInputMappingContext(PlayerController, CurrentGeneralInputMappingContext); AddInputMappingContext(PlayerController, CurrentMovementInputMappingContext); } 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()); } } }