Skip to content
Snippets Groups Projects
Select Git revision
  • e5f46ab642b00effb0a87c9dec97e25153583d67
  • dev/5.3 default
  • feature/scaleTeleport
  • feature/scaleAndHeightTeleport
  • 5.3
  • 5.0
  • TempNav
  • fix/DisplayClusterTemplateCode
  • fix_5.3/DisplayClusterTemplateFix
  • ReworkedToolkit
  • dev/5.2
  • feature/make_interaction_ray_accesible
  • dev/5.1
  • 4.27
  • 4.26 protected
  • 4.22
  • 4.21
17 results

TeleportationComponent.cpp

Blame
  • Forked from LuFG VR VIS / VR-Group / Unreal-Development / Plugins / RWTH VR Toolkit
    Source project has a limited visibility.
    TeleportationComponent.cpp 13.64 KiB
    // Fill out your copyright notice in the Description page of Project Settings.
    
    
    #include "Pawn/Navigation/TeleportationComponent.h"
    
    #include "EnhancedInputComponent.h"
    #include "NavigationSystem.h"
    #include "Engine/LocalPlayer.h"
    #include "GameFramework/PlayerController.h"
    #include "NiagaraFunctionLibrary.h"
    #include "Kismet/GameplayStatics.h"
    #include "NiagaraDataInterfaceArrayFunctionLibrary.h"
    #include "Utility/RWTHVRUtilities.h"
    #include "MotionControllerComponent.h"
    #include "Camera/CameraComponent.h"
    #include "Kismet/KismetMathLibrary.h"
    
    void UTeleportationComponent::SetupPlayerInput(UInputComponent* PlayerInputComponent)
    {
    	Super::SetupPlayerInput(PlayerInputComponent);
    
    	if (!VRPawn || !VRPawn->HasLocalNetOwner())
    	{
    		return;
    	}
    
    	TeleportTraceComponent = UNiagaraFunctionLibrary::SpawnSystemAtLocation(
    		GetWorld(), TeleportTraceSystem, VRPawn->GetActorLocation(), FRotator(0), FVector(1), true, true,
    		ENCPoolMethod::AutoRelease, true);
    
    	// simple way of changing the handedness
    	if (bMoveWithRightHand)
    	{
    		TeleportationHand = VRPawn->RightHand;
    		RotationHand = VRPawn->LeftHand;
    	}
    	else
    	{
    		TeleportationHand = VRPawn->LeftHand;
    		RotationHand = VRPawn->RightHand;
    	}
    
    	UEnhancedInputComponent* EI = Cast<UEnhancedInputComponent>(PlayerInputComponent);
    	if (!EI)
    	{
    		UE_LOG(Toolkit, Error, TEXT("Cannot cast Input Component to Enhanced Inpu Component in VRPawnMovement"));
    		return;
    	}
    
    	// teleporting
    	EI->BindAction(Teleport, ETriggerEvent::Started, this, &UTeleportationComponent::OnStartTeleportTrace);
    	EI->BindAction(Teleport, ETriggerEvent::Triggered, this, &UTeleportationComponent::UpdateTeleportTrace);
    	EI->BindAction(Teleport, ETriggerEvent::Completed, this, &UTeleportationComponent::OnEndTeleportTrace);
    	EI->BindAction(Teleport, ETriggerEvent::Canceled, this, &UTeleportationComponent::OnEndTeleportTrace);
    
    	EI->BindAction(CancelTeleport,ETriggerEvent::Started, this, &UTeleportationComponent::OnCancelTeleport);
    
    	// turning is defined in MovementComponentBase
    }
    
    void UTeleportationComponent::PlayHapticEffect(UHapticFeedbackEffect_Base* HapticEffect, float Intensity)
    {
    	Intensity = FMath::Clamp(Intensity, 0.f, 1.f);
    	APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
    	PlayerController->PlayHapticEffect(HapticEffect, EControllerHand::Right,Intensity);
    }
    
    void UTeleportationComponent::BeginPlay()
    {
    	Super::BeginPlay();
    
    	if (!BPTeleportVisualizer)
    	{
    		UE_LOG(Toolkit, Error,
    			   TEXT("SetupPlayerInput: BPTeleportVisualizer must be set to an Actor class that can be spawned!"));
    		return;
    	}
    
    	TeleportVisualizer =
    		GetWorld()->SpawnActor<ATeleportVisualizer>(BPTeleportVisualizer, VRPawn->GetActorLocation(), VRPawn->GetActorRotation());
    	
    	TeleportTraceComponent->SetVisibility(false);
    	TeleportVisualizer->SetActorHiddenInGame(true);
    	TeleportVisualizer->Platform->SetHiddenInGame(false);
    	FVector PawnBottomLocation = VRPawn->GetActorLocation();
    	PawnBottomLocation.Z = VRPawn->GetActorLocation().Z;
    	TeleportVisualizer->Platform->SetWorldLocation(PawnBottomLocation);
    	
    }
    
    void UTeleportationComponent::CleanupTeleportVisualization()
    {
    	if (!VRPawn)
    		return;
    	// End Teleport Trace
    	bTeleportTraceActive = false;
    	TeleportTraceComponent->SetVisibility(false);
    	TeleportVisualizer->SetActorHiddenInGame(true);
    	TeleportVisualizer->HorizontalIndicator->SetHiddenInGame(true);
    	TeleportVisualizer->VerticalIndicator->SetHiddenInGame(true);
    	//TeleportVisualizer->PreTravelPlatform->SetWorldLocation(TeleportVisualizer->Platform->GetComponentLocation());
    	bHasVerticalPhaseStarted=false;
    	bValidTeleportLocation = false;
    }
    
    void UTeleportationComponent::UpdateUserPreview()
    {
    	FVector NewHMDPos = TeleportVisualizer->PreTravelPlatform->GetComponentLocation();
    	NewHMDPos.Z += VRPawn->HeadCameraComponent->GetComponentLocation().Z - TeleportVisualizer->PreTravelPlatform->GetComponentLocation().Z; // works since the user stands on the platform (or at least on the same Z level)
    	
    	TeleportVisualizer->HMDPreview->SetRelativeLocation(FVector(0,0,NewHMDPos.Z));
    	TeleportVisualizer->HMDPreview->SetWorldRotation(VRPawn->HeadCameraComponent->GetComponentRotation());
    	
    	// Set rotation of vertical indicator arrows to always face pawn
    	FRotator RotationToPawn = UKismetMathLibrary::FindLookAtRotation(TeleportVisualizer->VerticalIndicator->GetComponentLocation(), VRPawn->GetActorLocation());
    	// only rotate around Z axis
    	RotationToPawn.Pitch = 0;
    	RotationToPawn.Roll = 0;
    	TeleportVisualizer->VerticalIndicator->SetRelativeRotation(RotationToPawn);
    }
    
    // On button press -> show teleport trace
    void UTeleportationComponent::OnStartTeleportTrace(const FInputActionValue& Value)
    {
    	// Start Trace
    	bTeleportTraceActive = true;
    	TeleportTraceComponent->SetVisibility(true);
    	TeleportVisualizer->SetActorHiddenInGame(false);
    	
    	// Reset cancel teleport functionality
    	bTeleportCancelled = false; 
    }
    
    void UTeleportationComponent::OnCancelTeleport(const FInputActionValue& Value)
    {
    	CleanupTeleportVisualization();
    	bTeleportCancelled = true;
    }
    
    void UTeleportationComponent::SetPreTravelInformationVisibility(bool bVisibility)
    {
    }
    
    void UTeleportationComponent::SelectHorizontalLocation()
    {
    	const FVector StartPosition = TeleportationHand->GetComponentLocation();
    	const FVector ForwardVector = TeleportationHand->GetForwardVector();
    
    	TArray<AActor> ActorsToIgnore;
    
    	FPredictProjectilePathParams PredictParams = FPredictProjectilePathParams(
    		TeleportProjectileRadius, StartPosition, TeleportLaunchSpeed * ForwardVector, 5.0, ECC_WorldStatic);
    
    	PredictParams.ActorsToIgnore.Add(VRPawn);
    	PredictParams.ActorsToIgnore.Add(TeleportVisualizer);
    
    	UGameplayStatics::PredictProjectilePath(GetWorld(), PredictParams, PredictResult);
    
    	const FVector HitLocation = PredictResult.HitResult.Location;
    	const bool bValidHit = PredictResult.HitResult.IsValidBlockingHit();
    	// check if this is a valid location to move to
    
    	FVector OutLocation;
    	const bool bValidProjection = IsValidTeleportLocation(PredictResult.HitResult, OutLocation);
    
    	if (bUseNavMesh)
    	{
    		SelectedHorizontalPosition = OutLocation;
    		if (bValidTeleportLocation != bValidProjection)
    		{
    			bValidTeleportLocation = bValidProjection;
    			TeleportVisualizer->SetActorHiddenInGame(!bValidTeleportLocation);
    		}
    	}
    	else
    	{
    		if (bValidHit)
    		{
    			SelectedHorizontalPosition = HitLocation;
    			// Don't sweep on first call to correctly initialize platform position
    			if(bIsFirstHorizontalCall)
    			{
    				TeleportVisualizer->PreTravelPlatform->SetWorldLocation(SelectedHorizontalPosition, false); // teleport pre travel mesh to impact point
    				bIsFirstHorizontalCall = false;
    			}
    		}
    	}
    
    	TArray<FVector> PathPoints;
    	PathPoints.Add(StartPosition);
    	for (FPredictProjectilePathPointData PData : PredictResult.PathData)
    	{
    		PathPoints.Add(PData.Location);
    	}
    	UNiagaraDataInterfaceArrayFunctionLibrary::SetNiagaraArrayVector(TeleportTraceComponent, FName("User.PointArray"),
    																	 PathPoints);
    }
    
    void UTeleportationComponent::SelectVerticalLocation()
    {
    		float OpeningAngle = GetAngleBetweenVectors(ReferenceVector, VRPawn->RightHand->GetForwardVector());
    	
        // don't change height if angle is below deadzone and the deadzone has never been left
        if(abs(OpeningAngle) <= Deadzone && !hasLeftDeadZone)
        {
            //SetVerticalActorColor(FLinearColor::Gray);
            PlayHapticEffect(TeleportVisualizer->HapticFeedback,0.1);
            return;
        }
        //SetVerticalActorColor(FLinearColor::Blue); // indicate that vertical selection is now active only if the deadzone has been left
    	
        // if this is the first time the deadzone is left, play faint haptic cue to indicate that
        if (hasLeftDeadZone == false) 
        {
            PlayHapticEffect(TeleportVisualizer->HapticFeedback, 0.1); // 
            hasLeftDeadZone = true;// disable deadzone check if height change has begun
        }
    
        //UE_LOG(LogTemp, Warning, TEXT("OpeningAngle = %f"), OpeningAngle);
        FVector NewPos = SelectedHorizontalPosition;
        
        // "1:1" Movement if angle is below velocity zone
        if (abs(OpeningAngle) < VelocityZone)
        {
            float FixedOffset = OpeningAngle * ElevationSpeed;
        	AngularOffset = OpeningAngle * ElevationSpeed;
    
            NewPos.Z = NewPos.Z + AngularOffset + HeightOffset; // make movements correspond directly to controller angle
            //NewPos.Z += HeightOffset; // account for height changes made in the velocity zone
            TeleportVisualizer->PreTravelPlatform->SetWorldLocation(NewPos, true);
        }
        else // velocity-based movements if angle is within velocity zone
        {
            float Sign = (OpeningAngle < 0) ? -1 : 1; // determine the sign based on the opening angle
    
            float OvershootAngle = (abs(OpeningAngle) - VelocityZone) * Sign; // determines how far we are above/below the velocity zone
    
            /*float OldHeightOffset = HeightOffset;*/
    
            HeightOffset += OvershootAngle * VelocitySpeed;
    
            NewPos.Z += HeightOffset + AngularOffset;
            /*bool bWasMoved =*/ TeleportVisualizer->PreTravelPlatform->SetWorldLocation(NewPos, true); // change location and check whether movement occurred or if platform was hitting ground
    
            /*if(!bWasMoved)
            {
                HeightOffset = OldHeightOffset; //don't change height offset if the platform wasn't moved
            }*/
        }
    }
    
    float UTeleportationComponent::GetAngleBetweenVectors(FVector Vector1, FVector Vector2)
    {
    	AngleProjectionPlane = FPlane(VRPawn->RightHand->GetComponentLocation(), VRPawn->RightHand->GetComponentLocation() + ReferenceVector, VRPawn->RightHand->GetComponentLocation() + FVector(0, 0, 1));
    
    	FVector MCLoc = VRPawn->RightHand->GetComponentLocation();
    
    	Vector1 = Vector1.GetSafeNormal();
    	Vector2 = Vector2.GetSafeNormal();
    
    	// Mapping the vectors to a plane somehow helps with jittery behaviour ?!
    	Vector1 = FVector::VectorPlaneProject(Vector1, AngleProjectionPlane.GetNormal());
    	Vector2 = FVector::VectorPlaneProject(Vector2, AngleProjectionPlane.GetNormal());
    
    	// Uncomment this to see the projection
    	//DrawDebugSolidPlane(GetWorld(), AngleProjectionPlane, RightMC->GetComponentLocation(), 50, FColor::Magenta, false, 1);
    	//DrawDebugDirectionalArrow(GetWorld(), MCLoc, MCLoc + Vector1 * 30, 5, FColor::Red, false, -1,1,2);
    	//DrawDebugDirectionalArrow(GetWorld(), MCLoc, MCLoc + Vector2 * 30, 5, FColor::Green, false, -1,1,2);
        
    	FVector CrossProduct = FVector::CrossProduct(Vector1,Vector2);
    
    	//DrawDebugDirectionalArrow(GetWorld(), MCLoc, MCLoc + PlaneNormal * 30, 5, FColor::Cyan, false, -1, 1, 2);
        
    	float OpeningAngle = FMath::Atan2(FVector::CrossProduct(Vector1, Vector2).Size(), FVector::DotProduct(Vector1, Vector2));
    	OpeningAngle = FMath::RadiansToDegrees(OpeningAngle);
        
    	// Take sign of cross product to determine if angle was opened upwards or downwards
    	float Distance = AngleProjectionPlane.PlaneDot(MCLoc + CrossProduct * 30);
    	return FMath::Sign(Distance) * OpeningAngle;
    }
    
    // called while button is pressed (Triggered)
    void UTeleportationComponent::UpdateTeleportTrace(const FInputActionValue& Value)
    {
    	
    	float AxisValue = Value.Get<float>();
    	
    	// If we have cancelled the teleport trace via a button press, return.
    	// Only allow teleportation again if teleport button is fully let down and pressed again
    	if(bTeleportCancelled)
    	{
    		return;
    	}
    	
    	UpdateUserPreview();
    
    	if(TeleportState == ETeleportState::BaseState)
    	{
    		SetPreTravelInformationVisibility(true);
    	}
    
    	if(TeleportState != ETeleportState::VerticalSelection)
    	{
    		if(AxisValue <= TwoStepThreshold)
    		{
    			TeleportState = ETeleportState::HorizontalSelection;
    		}
    		if(AxisValue > TwoStepThreshold && bHorizontalPhaseCompleted)
    		{
    			if(!bHasVerticalPhaseStarted)
    			{
    				PlayHapticEffect(TeleportVisualizer->HapticFeedback,1);
    				bHasVerticalPhaseStarted = true;
    			}
    
    			TeleportState = ETeleportState::VerticalSelection;
    			// store current forward vector of right hand for further computations in the height selection phase
    			ReferenceVector = VRPawn->RightHand->GetForwardVector();
    		}
    	}
    
    	switch (TeleportState)
    	{
    	case ETeleportState::BaseState:
    		SetPreTravelInformationVisibility(true);
    		TeleportState = ETeleportState::HorizontalSelection;
    		break;
    
    	case ETeleportState::HorizontalSelection:
    		TeleportVisualizer->HorizontalIndicator->SetHiddenInGame(false);
    		SelectHorizontalLocation();
    		bHorizontalPhaseCompleted = true;
    		break;
    
    	case ETeleportState::VerticalSelection:
    		// Update the teleport trace
    			TeleportVisualizer->HorizontalIndicator->SetHiddenInGame(true);
    		TeleportVisualizer->VerticalIndicator->SetHiddenInGame(false);
    		SelectVerticalLocation();
    		break;
    
    	default:
    		UE_LOG(LogTemp,Error,TEXT("TeleportationComponent: Invalid Teleport Phase"))
    		break;
    	}
    	
    }
    
    bool UTeleportationComponent::IsValidTeleportLocation(const FHitResult& Hit, FVector& ProjectedLocation) const
    {
    	UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
    	const FNavAgentProperties& AgentProps = FNavAgentProperties(15, 160);
    	FNavLocation ProjectedNavLocation;
    	const bool bProjectPoint =
    		(NavSys &&
    		 NavSys->ProjectPointToNavigation(Hit.Location, ProjectedNavLocation, INVALID_NAVEXTENT, &AgentProps));
    	ProjectedLocation = ProjectedNavLocation.Location;
    	return bProjectPoint /*&& Hit.IsValidBlockingHit()*/;
    }
    
    // On button release -> remove trace and teleport user to location
    void UTeleportationComponent::OnEndTeleportTrace(const FInputActionValue& Value)
    {
    	TeleportState=ETeleportState::BaseState;
    	HeightOffset=0; //reset height offset for next teleport
    	hasLeftDeadZone=false;
    	bIsFirstHorizontalCall=true;
    	bHorizontalPhaseCompleted=false;
    	
    	// If teleport was cancelled via button press, do not teleport.
    	// Allow teleportation again, if we let go and press the teleport button again.
    	if(bTeleportCancelled)
    	{
    		return;
    	}
    	CleanupTeleportVisualization();
    	VRPawn->TeleportTo(TeleportVisualizer->PreTravelPlatform->GetComponentLocation(), VRPawn->GetActorRotation());
    }