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 +};