diff --git a/Content/Components/Movement/Teleportation/BP_TeleportVisualizer.uasset b/Content/Components/Movement/Teleportation/BP_TeleportVisualizer.uasset
new file mode 100644
index 0000000000000000000000000000000000000000..a0ead14ac8be1f1441c33bc164b6f8cfe30ef48b
Binary files /dev/null and b/Content/Components/Movement/Teleportation/BP_TeleportVisualizer.uasset differ
diff --git a/Content/Components/Movement/Teleportation/BP_TeleportationComponent.uasset b/Content/Components/Movement/Teleportation/BP_TeleportationComponent.uasset
index 724895615850f01d9d665e8b40569d5829fc4574..d924129f28babb468384a1d11df4dd09f7fa173b 100644
Binary files a/Content/Components/Movement/Teleportation/BP_TeleportationComponent.uasset and b/Content/Components/Movement/Teleportation/BP_TeleportationComponent.uasset differ
diff --git a/Content/Components/Movement/Teleportation/IA_CancelTeleport.uasset b/Content/Components/Movement/Teleportation/IA_CancelTeleport.uasset
new file mode 100644
index 0000000000000000000000000000000000000000..525d3a24f3d733a1622caf78160c78b2831949f8
Binary files /dev/null and b/Content/Components/Movement/Teleportation/IA_CancelTeleport.uasset differ
diff --git a/Content/Components/Movement/Teleportation/IA_Teleport.uasset b/Content/Components/Movement/Teleportation/IA_Teleport.uasset
new file mode 100644
index 0000000000000000000000000000000000000000..b61835ef8f1cb9c8e61b909f55c09dfa8c8573c3
Binary files /dev/null and b/Content/Components/Movement/Teleportation/IA_Teleport.uasset differ
diff --git a/Content/Input/Default_IMC/IMC_MovementLeftHand.uasset b/Content/Input/Default_IMC/IMC_MovementLeftHand.uasset
index a688bcc47ef30ebaeeb28592df4a0c2c00ee4c68..832028be9f93d9fa27cf449f845e167c17f8449b 100644
Binary files a/Content/Input/Default_IMC/IMC_MovementLeftHand.uasset and b/Content/Input/Default_IMC/IMC_MovementLeftHand.uasset differ
diff --git a/Content/Input/Default_IMC/IMC_MovementRightHand.uasset b/Content/Input/Default_IMC/IMC_MovementRightHand.uasset
index aa1f8437dcb5cb367674b6c27fc1a84357dac27a..9cde391a2c9ab9548b636961ceab0c5946d09c79 100644
Binary files a/Content/Input/Default_IMC/IMC_MovementRightHand.uasset and b/Content/Input/Default_IMC/IMC_MovementRightHand.uasset differ
diff --git a/Source/RWTHVRToolkit/Private/Pawn/Navigation/TeleportationComponent.cpp b/Source/RWTHVRToolkit/Private/Pawn/Navigation/TeleportationComponent.cpp
index 3a1a7e054daf3e2f3e544313ee8505316ada9ff5..c2ea22ca7ebd2d3b10742f5004890f4a619545e1 100644
--- a/Source/RWTHVRToolkit/Private/Pawn/Navigation/TeleportationComponent.cpp
+++ b/Source/RWTHVRToolkit/Private/Pawn/Navigation/TeleportationComponent.cpp
@@ -12,7 +12,8 @@
 #include "NiagaraDataInterfaceArrayFunctionLibrary.h"
 #include "Utility/RWTHVRUtilities.h"
 #include "MotionControllerComponent.h"
-
+#include "Camera/CameraComponent.h"
+#include "Kismet/KismetMathLibrary.h"
 
 void UTeleportationComponent::SetupPlayerInput(UInputComponent* PlayerInputComponent)
 {
@@ -35,10 +36,14 @@ void UTeleportationComponent::SetupPlayerInput(UInputComponent* PlayerInputCompo
 	}
 
 	TeleportVisualizer =
-		GetWorld()->SpawnActor<AActor>(BPTeleportVisualizer, VRPawn->GetActorLocation(), VRPawn->GetActorRotation());
-
+		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);
 
 	// simple way of changing the handedness
 	if (bMoveWithRightHand)
@@ -60,14 +65,60 @@ void UTeleportationComponent::SetupPlayerInput(UInputComponent* PlayerInputCompo
 	}
 
 	// teleporting
-	EI->BindAction(Move, ETriggerEvent::Started, this, &UTeleportationComponent::OnStartTeleportTrace);
-	EI->BindAction(Move, ETriggerEvent::Triggered, this, &UTeleportationComponent::UpdateTeleportTrace);
-	EI->BindAction(Move, ETriggerEvent::Completed, this, &UTeleportationComponent::OnEndTeleportTrace);
-	EI->BindAction(Move, ETriggerEvent::Canceled, this, &UTeleportationComponent::OnEndTeleportTrace);
+	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::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->GetActorLocation();
+	NewHMDPos.Z += VRPawn->HeadCameraComponent->GetComponentLocation().Z - TeleportVisualizer->Platform->GetComponentLocation().Z; // works since the user stands on the platform (or at least on the same Z level)
+	
+	TeleportVisualizer->HMDPreview->SetWorldLocationAndRotation(NewHMDPos, VRPawn->HeadCameraComponent->GetComponentRotation());
+	
+	// set rotation of bodypreview
+	TeleportVisualizer->BodyPreview->SetWorldRotation(FRotator(0.0,-90,0));
+
+	FVector NewBodyPreviewPos = TeleportVisualizer->HMDPreview->GetComponentLocation();
+	NewBodyPreviewPos -= FVector(0, 0, TeleportVisualizer->HeadBodyDistance);
+	TeleportVisualizer->BodyPreview->SetWorldLocation(NewBodyPreviewPos);
+	
+	// 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)
 {
@@ -75,12 +126,23 @@ void UTeleportationComponent::OnStartTeleportTrace(const FInputActionValue& Valu
 	bTeleportTraceActive = true;
 	TeleportTraceComponent->SetVisibility(true);
 	TeleportVisualizer->SetActorHiddenInGame(false);
+	
+	// Reset cancel teleport functionality
+	bTeleportCancelled = false; 
 }
 
-// called while button is pressed (Triggered)
-void UTeleportationComponent::UpdateTeleportTrace(const FInputActionValue& Value)
+void UTeleportationComponent::OnCancelTeleport(const FInputActionValue& Value)
+{
+	CleanupTeleportVisualization();
+	bTeleportCancelled = true;
+}
+
+void UTeleportationComponent::SetPreTravelInformationVisibility(bool bVisibility)
+{
+}
+
+void UTeleportationComponent::SelectHorizontalLocation()
 {
-	// Update the teleport trace
 	const FVector StartPosition = TeleportationHand->GetComponentLocation();
 	const FVector ForwardVector = TeleportationHand->GetForwardVector();
 
@@ -103,7 +165,7 @@ void UTeleportationComponent::UpdateTeleportTrace(const FInputActionValue& Value
 
 	if (bUseNavMesh)
 	{
-		FinalTeleportLocation = OutLocation;
+		SelectedHorizontalPosition = OutLocation;
 		if (bValidTeleportLocation != bValidProjection)
 		{
 			bValidTeleportLocation = bValidProjection;
@@ -114,10 +176,13 @@ void UTeleportationComponent::UpdateTeleportTrace(const FInputActionValue& Value
 	{
 		if (bValidHit)
 		{
-			FinalTeleportLocation = HitLocation;
-			TeleportVisualizer->SetActorHiddenInGame(false);
-			// update location
-			TeleportVisualizer->SetActorLocation(FinalTeleportLocation);
+			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;
+			}
 		}
 	}
 
@@ -131,6 +196,156 @@ void UTeleportationComponent::UpdateTeleportTrace(const FInputActionValue& Value
 																	 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());
@@ -146,13 +361,18 @@ bool UTeleportationComponent::IsValidTeleportLocation(const FHitResult& Hit, FVe
 // On button release -> remove trace and teleport user to location
 void UTeleportationComponent::OnEndTeleportTrace(const FInputActionValue& Value)
 {
-	if (!VRPawn)
+	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;
-	// End Teleport Trace
-	bTeleportTraceActive = false;
-	TeleportTraceComponent->SetVisibility(false);
-	TeleportVisualizer->SetActorHiddenInGame(true);
-
-	bValidTeleportLocation = false;
-	VRPawn->TeleportTo(FinalTeleportLocation, VRPawn->GetActorRotation());
+	}
+	CleanupTeleportVisualization();
+	VRPawn->TeleportTo(TeleportVisualizer->PreTravelPlatform->GetComponentLocation(), VRPawn->GetActorRotation());
 }
diff --git a/Source/RWTHVRToolkit/Private/UI/TeleportVisualizer.cpp b/Source/RWTHVRToolkit/Private/UI/TeleportVisualizer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4d4fd2e45251e0f07932884d293664aeb645da62
--- /dev/null
+++ b/Source/RWTHVRToolkit/Private/UI/TeleportVisualizer.cpp
@@ -0,0 +1,125 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "UI/TeleportVisualizer.h"
+
+#include "MovieSceneSequenceID.h"
+#include "NiagaraComponent.h"
+#include "Engine/StaticMeshActor.h"
+
+// Sets default values
+ATeleportVisualizer::ATeleportVisualizer()
+{
+ 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
+	PrimaryActorTick.bCanEverTick = true;
+	
+	RootComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Root"));
+	Platform = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("PlatformMesh"));
+
+	//Platform->SetStaticMesh(PlatformMesh);
+	
+	// disable collisions of platform meshes
+	FCollisionResponseContainer ResponseContainerPlatform;
+	ResponseContainerPlatform.SetAllChannels(ECollisionResponse::ECR_Ignore);
+
+	PreTravelPlatform = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("PreTravelPlatformMesh"));
+	PreTravelPlatform->SetCollisionResponseToChannels(ResponseContainerPlatform); // Ignore collisions on all channels ..
+	PreTravelPlatform->SetCollisionResponseToChannel(ECollisionChannel::ECC_WorldStatic, ECollisionResponse::ECR_Block); // .. except WorldStatic channel
+	PreTravelPlatform->SetMobility(EComponentMobility::Movable);
+	
+	// ### HMD Preview ###
+
+	// Create a static mesh component for the HMD preview
+	HMDPreview = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("HMD_PreviewMesh"));
+	HMDPreview->SetupAttachment(PreTravelPlatform);
+	
+
+	//make HMD mesh slightly bigger for better visibility
+	FVector Scale = HMDPreview->GetRelativeScale3D();
+	Scale *= 1.2f;
+	HMDPreview->SetRelativeScale3D(Scale);
+	HMDPreview->SetCollisionEnabled(ECollisionEnabled::NoCollision); // disable collision
+	
+	
+	// Create body preview mesh which visualizes the approximate position of the users body
+	BodyPreview = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BodyPreviewMesh"));
+	BodyPreview->SetupAttachment(HMDPreview);
+	BodyPreview->SetCollisionEnabled(ECollisionEnabled::NoCollision); // disable collision
+	
+	// ### Ground Plane ###
+	
+	// Create a static mesh component for the HMD preview
+	GroundPlane = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("GroundPlaneMesh"));
+
+	FCollisionResponseContainer ResponseContainerGroundPlane;
+	ResponseContainerGroundPlane.SetAllChannels(ECollisionResponse::ECR_Ignore);
+	GroundPlane->SetCollisionResponseToChannels(ResponseContainerGroundPlane); // Ignore collisions on all channels ..
+	GroundPlane->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Block); // .. except pawn channel
+	
+	GroundVisualizationNiagaraComponent = CreateDefaultSubobject<UNiagaraComponent>(TEXT("GroundParticleSystem"));
+	GroundVisualizationNiagaraComponent->SetupAttachment(RootComponent);  // Attach it to the root component or any other desired parent component
+	
+	// setup visuals (migrated from CreateTwoStepHandler function)
+
+	HorizontalIndicator = CreateDefaultSubobject<UChildActorComponent>("HorizontalIndicator");
+
+	
+	VerticalIndicator = CreateDefaultSubobject<UChildActorComponent>("VerticalIndicator");
+
+
+	DefaultHorizontalRange = 4000;
+
+
+}
+
+// Called when the game starts or when spawned
+void ATeleportVisualizer::BeginPlay()
+{
+	Super::BeginPlay();
+
+	FActorSpawnParameters SpawnParameters;
+	
+	HMDPreview->AttachToComponent(PreTravelPlatform,FAttachmentTransformRules(
+												EAttachmentRule::KeepRelative,
+												EAttachmentRule::KeepWorld,
+												EAttachmentRule::KeepRelative, false));
+
+
+	BodyPreview->AttachToComponent(HMDPreview, FAttachmentTransformRules(
+											EAttachmentRule::KeepRelative,
+											EAttachmentRule::KeepWorld,
+											EAttachmentRule::KeepRelative, false));
+
+
+	GroundPlane->SetMaterial(0,GroundPlaneMaterial);
+	GroundPlane->AttachToComponent(PreTravelPlatform, FAttachmentTransformRules(
+											EAttachmentRule::SnapToTarget,
+											EAttachmentRule::KeepWorld,
+											EAttachmentRule::KeepRelative,
+											true));
+
+	GroundVisualizationNiagaraComponent->SetAsset(GroundVisualizationNiagaraSystem);
+	
+	HorizontalIndicator->AttachToComponent(PreTravelPlatform, FAttachmentTransformRules(
+															EAttachmentRule::SnapToTarget,
+															EAttachmentRule::SnapToTarget,
+															EAttachmentRule::KeepWorld,true));
+	
+	VerticalIndicator->AttachToComponent(PreTravelPlatform, FAttachmentTransformRules(
+															EAttachmentRule::SnapToTarget,
+															EAttachmentRule::SnapToTarget,
+															EAttachmentRule::KeepWorld,true));
+	
+	FVector HMDLocation = HMDPreview->GetComponentLocation();
+	HMDLocation.Z -= HeadBodyDistance;
+	BodyPreview->SetWorldLocation(HMDLocation);
+	//GroundVisualizationNiagaraComponent->SetNiagaraVariableFloat("User.BeamWidth", GroundConnectorWidth); // set default width*/
+}
+
+// Called every frame
+void ATeleportVisualizer::Tick(float DeltaTime)
+{
+	Super::Tick(DeltaTime);
+
+}
+
diff --git a/Source/RWTHVRToolkit/Public/Pawn/Navigation/TeleportationComponent.h b/Source/RWTHVRToolkit/Public/Pawn/Navigation/TeleportationComponent.h
index 04fbe704c81473e540c2ad000fbab35f11a1f2b0..5b8db18fc9ac2d14a985716d5f62b20ca4bf2f6c 100644
--- a/Source/RWTHVRToolkit/Public/Pawn/Navigation/TeleportationComponent.h
+++ b/Source/RWTHVRToolkit/Public/Pawn/Navigation/TeleportationComponent.h
@@ -8,9 +8,20 @@
 #include "NiagaraComponent.h"
 #include "Kismet/GameplayStaticsTypes.h"
 #include "Pawn/Navigation/MovementComponentBase.h"
+#include "UI/TeleportVisualizer.h"
 
 #include "TeleportationComponent.generated.h"
 
+
+UENUM(BlueprintType)
+enum class ETeleportState : uint8
+{
+	BaseState UMETA(DisplayName = "Base/Idle State"),
+	HorizontalSelection UMETA(DisplayName = "Horizontal Location Selection"),
+	VerticalSelection UMETA(DisplayName = "Vertical Location Selection ")
+};
+
+
 UCLASS(Blueprintable)
 class RWTHVRTOOLKIT_API UTeleportationComponent : public UMovementComponentBase
 {
@@ -27,6 +38,14 @@ public:
 	UPROPERTY(VisibleAnywhere, Category = "VR Movement|Teleport")
 	bool bUseNavMesh = false;
 
+	/**
+	 * If true, target location will not get projected onto the geometry.
+	 * Height of target location is defined in a second step, by holding the teleportation button
+	 * and moving the controller up or down.
+	 */
+	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VR Movement|Teleport")
+	bool bHeighTeleport = false;
+
 	/**
 	 * Speed at which the projectile shoots out from the controller to get the teleport location
 	 * Higher values = larger teleportation range
@@ -35,7 +54,10 @@ public:
 	float TeleportLaunchSpeed = 800;
 
 	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VR Movement|Input|Actions")
-	UInputAction* Move;
+	UInputAction* Teleport;
+	
+	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VR Movement|Input|Actions")
+	UInputAction* CancelTeleport;
 
 	// Trace Visualization
 	UPROPERTY(EditAnywhere)
@@ -46,22 +68,38 @@ public:
 
 	UPROPERTY()
 	UNiagaraComponent* TeleportTraceComponent;
-
+	
 	/*Movement Input*/
 	UFUNCTION(BlueprintCallable)
 	void OnStartTeleportTrace(const FInputActionValue& Value);
 
+	void SetPreTravelInformationVisibility(bool bVisibility);
+
+	void SelectHorizontalLocation();
+
+	void SelectVerticalLocation();
+	
+	float GetAngleBetweenVectors(FVector Vector1, FVector Vector2);
+
 	UFUNCTION(BlueprintCallable)
 	void UpdateTeleportTrace(const FInputActionValue& Value);
-	bool IsValidTeleportLocation(const FHitResult& Hit, FVector& ProjectedLocation) const;
 
 	UFUNCTION(BlueprintCallable)
 	void OnEndTeleportTrace(const FInputActionValue& Value);
 
+	UFUNCTION(BlueprintCallable)
+	void OnCancelTeleport(const FInputActionValue& Value);
+
+	bool IsValidTeleportLocation(const FHitResult& Hit, FVector& ProjectedLocation) const;
+
+	
 	virtual void SetupPlayerInput(UInputComponent* PlayerInputComponent) override;
 
 	UPROPERTY(BlueprintReadOnly)
-	AActor* TeleportVisualizer;
+	ATeleportVisualizer* TeleportVisualizer;
+
+	void PlayHapticEffect(UHapticFeedbackEffect_Base* HapticEffect, float Intensity);
+	
 private:
 	UPROPERTY()
 	UMotionControllerComponent* TeleportationHand;
@@ -72,8 +110,59 @@ private:
 	bool bTeleportTraceActive;
 	float TeleportProjectileRadius = 3.6;
 	float RotationArrowRadius = 10.0;
+
+	UPROPERTY()
 	FPredictProjectilePathResult PredictResult;
+
 	bool bValidTeleportLocation = false;
+
+	UPROPERTY()
 	FVector FinalTeleportLocation;
+
+	UPROPERTY()
+	float TwoStepThreshold = 0.99f;
 	
-};
+	void CleanupTeleportVisualization();
+	bool bTeleportCancelled = false;
+
+	void UpdateUserPreview();
+
+	UPROPERTY()
+	ETeleportState TeleportState = ETeleportState::BaseState;
+
+	UPROPERTY()
+	FVector ReferenceVector;
+
+	bool bHorizontalPhaseCompleted;
+	bool bHasVerticalPhaseStarted;
+
+	UPROPERTY()
+	float Deadzone = 10;
+
+	UPROPERTY()
+	float VelocityZone = 30;
+
+	UPROPERTY()
+	float ElevationSpeed = 6; // speed for height changes below the velocity zone
+
+	UPROPERTY()
+	float VelocitySpeed = 0.2; // speed for height changes in the velocity zone
+	 
+	UPROPERTY()
+	bool hasLeftDeadZone = false; // used to apply the deadzone limitation only when no height change has been made
+
+	UPROPERTY()
+	float HeightOffset = 0;
+
+	UPROPERTY()
+	float AngularOffset = 0;
+	
+	UPROPERTY()
+	FVector SelectedHorizontalPosition;
+	UPROPERTY()
+	FPlane AngleProjectionPlane;
+
+	UPROPERTY()
+	bool bIsFirstHorizontalCall = true;
+	
+};
\ No newline at end of file
diff --git a/Source/RWTHVRToolkit/Public/UI/TeleportVisualizer.h b/Source/RWTHVRToolkit/Public/UI/TeleportVisualizer.h
new file mode 100644
index 0000000000000000000000000000000000000000..85f164cd02535ff0864f69aa870bdaa1f1c2c379
--- /dev/null
+++ b/Source/RWTHVRToolkit/Public/UI/TeleportVisualizer.h
@@ -0,0 +1,101 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "NiagaraComponent.h"
+#include "Components/CapsuleComponent.h"
+#include "GameFramework/Actor.h"
+#include "TeleportVisualizer.generated.h"
+
+UCLASS(Blueprintable)
+class RWTHVRTOOLKIT_API ATeleportVisualizer : public AActor
+{
+	GENERATED_BODY()
+	
+public:	
+	// Sets default values for this actor's properties
+	ATeleportVisualizer();
+
+protected:
+	// Called when the game starts or when spawned
+	virtual void BeginPlay() override;
+
+public:	
+	// Called every frame
+	virtual void Tick(float DeltaTime) override;
+
+	UPROPERTY(EditAnywhere)
+	UStaticMeshComponent* Platform;
+
+	UPROPERTY(EditAnywhere)
+	UCapsuleComponent* HorizontalCollisionChecker;
+
+	UPROPERTY(EditAnywhere)
+	UStaticMeshComponent* PreTravelPlatform;
+	
+	/*UPROPERTY(EditAnywhere)
+	UStaticMesh* PlatformMesh;
+	
+	UPROPERTY(EditAnywhere)
+	UStaticMesh* HMDPreviewMesh;
+	
+	UPROPERTY(EditAnywhere)
+	UStaticMesh* BodyPreviewMesh;
+	
+	UPROPERTY(EditAnywhere)
+	UStaticMesh* GroundPlaneMesh;*/
+
+	/*
+	UPROPERTY(EditAnywhere)
+	TSubclassOf<AActor> HorizontalIndicatorActor;
+	
+	UPROPERTY(EditAnywhere)
+	TSubclassOf<AActor> VerticalIndicatorActor;
+	*/
+	
+	UPROPERTY(EditAnywhere)
+	UNiagaraSystem* GroundVisualizationNiagaraSystem;
+	
+	UPROPERTY(EditAnywhere)
+	UStaticMeshComponent* BodyPreview;
+
+	UPROPERTY(EditAnywhere)
+	UStaticMeshComponent* HMDPreview;
+
+	UPROPERTY(EditAnywhere)
+	UStaticMeshComponent* GroundPlane;
+
+	UPROPERTY(EditAnywhere)
+	UMaterialInterface* GroundPlaneMaterial;
+
+	UPROPERTY(EditAnywhere)
+	UNiagaraComponent* GroundVisualizationNiagaraComponent;
+
+
+	float DefaultHorizontalRange;
+
+	UPROPERTY(EditAnywhere)
+	float TwoStepDeadZone = 0.1f;
+
+	// float determining the distance between the HMD Preview mesh and the BodyPreviewMesh
+	UPROPERTY(EditAnywhere)
+	float HeadBodyDistance = 55;
+
+	UPROPERTY(EditAnywhere)
+	UHapticFeedbackEffect_Base* HapticFeedback;
+	
+	UMaterialInstanceDynamic* DynamicVerticalIndicatorMaterial;
+	
+	UPROPERTY(EditAnywhere)
+	UChildActorComponent* HorizontalIndicator;
+	
+	UPROPERTY(EditAnywhere)
+	UChildActorComponent* VerticalIndicator;
+
+private:
+
+
+	float GroundConnectorHeight = 100; // The threshold for how far above the ground a ground visualization should be visualized
+	float GroundConnectorWidth = 60.f; // determines how wide the ground connection visualization should be 
+};