diff --git a/Content/BP_VirtualRealityPawn.uasset b/Content/BP_VirtualRealityPawn.uasset index 4c150048c03e3d2bd214195ab0b680a67ecba972..47c76b941807f2128d88b3f0fa1d5c399257aaea 100644 Binary files a/Content/BP_VirtualRealityPawn.uasset and b/Content/BP_VirtualRealityPawn.uasset differ diff --git a/Content/Components/WidgetInteraction/BP_VRWidgetInteractionComponent.uasset b/Content/Components/WidgetInteraction/BP_VRWidgetInteractionComponent.uasset new file mode 100644 index 0000000000000000000000000000000000000000..5ce64da195cb87fc1e8952fc0c106da63a2e1432 Binary files /dev/null and b/Content/Components/WidgetInteraction/BP_VRWidgetInteractionComponent.uasset differ diff --git a/Content/Components/WidgetInteraction/IA_WidgetClick.uasset b/Content/Components/WidgetInteraction/IA_WidgetClick.uasset new file mode 100644 index 0000000000000000000000000000000000000000..1e8b6c8dbba9ffe7beac263c84b4c800c521e468 Binary files /dev/null and b/Content/Components/WidgetInteraction/IA_WidgetClick.uasset differ diff --git a/Content/Components/WidgetInteraction/IMCWidgetInteraction.uasset b/Content/Components/WidgetInteraction/IMCWidgetInteraction.uasset new file mode 100644 index 0000000000000000000000000000000000000000..6135a5b20e421c7fd3784230af445a929ac91e35 Binary files /dev/null and b/Content/Components/WidgetInteraction/IMCWidgetInteraction.uasset differ diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactors/VRWidgetInteractionComponent.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactors/VRWidgetInteractionComponent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5e41c0265d99cb11f8ba5174e3be01e5e00cc1c3 --- /dev/null +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactors/VRWidgetInteractionComponent.cpp @@ -0,0 +1,154 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Interaction/Interactors/VRWidgetInteractionComponent.h" + +#include "EnhancedInputComponent.h" +#include "EnhancedInputSubsystems.h" +#include "Interaction/Interactors/GrabComponent.h" +#include "Misc/Optional.h" +#include "Utility/VirtualRealityUtilities.h" + +UVRWidgetInteractionComponent::UVRWidgetInteractionComponent() +{ + PrimaryComponentTick.bCanEverTick = true; +} + +void UVRWidgetInteractionComponent::SetupPlayerInput(UInputComponent* PlayerInputComponent) +{ + IInputExtensionInterface::SetupPlayerInput(PlayerInputComponent); + + const APawn* Pawn = Cast<APawn>(GetOwner()); + if (!Pawn) + return; + + auto* InputSubsystem = GetEnhancedInputLocalPlayerSubsystem(Pawn); + if (!InputSubsystem) + return; + + // Because we cannot use the regular debug ray (only works in editor), we set up our own (mesh) ray. + SetupInteractionRay(); + + // add Input Mapping context + InputSubsystem->AddMappingContext(IMCWidgetInteraction, 0); + + UEnhancedInputComponent* EI = Cast<UEnhancedInputComponent>(Pawn->InputComponent); + if (EI == nullptr) + return; + + EI->BindAction(WidgetClickInputAction, ETriggerEvent::Started, this, &UVRWidgetInteractionComponent::OnBeginClick); + EI->BindAction(WidgetClickInputAction, ETriggerEvent::Completed, this, &UVRWidgetInteractionComponent::OnEndClick); +} + +// Called every frame +void UVRWidgetInteractionComponent::TickComponent(float DeltaTime, ELevelTick TickType, + FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + // only raytrace for targetable objects if user wants to enable this feature + if (!bCanRaytraceEveryTick) + return; + + // Disable/enable ray on hover + if (InteractionRayVisibility == EInteractionRayVisibility::VisibleOnHoverOnly) + { + if (IsOverInteractableWidget()) + { + InteractionRay->SetVisibility(true); + } + else + { + InteractionRay->SetVisibility(false); + } + } +} + +void UVRWidgetInteractionComponent::SetInteractionRayVisibility(EInteractionRayVisibility NewVisibility) +{ + InteractionRayVisibility = NewVisibility; + InteractionRay->SetVisibility(NewVisibility == Visible); + + if (InteractionRayVisibility == EInteractionRayVisibility::VisibleOnHoverOnly && !bCanRaytraceEveryTick) + { + UE_LOG(Toolkit, Warning, + TEXT("VisibleOnHoverOnly needs bCanRaytraceEveryTick=true, so this is forcibly set!")); + bCanRaytraceEveryTick = true; + } + if (InteractionRayVisibility == EInteractionRayVisibility::Visible && !bCanRaytraceEveryTick) + { + UE_LOG(Toolkit, Warning, + TEXT( + "VisibleOnHoverOnly will need two clicks to interact with widgets if bCanRaytraceEveryTick is not set!" + )); + } +} + +// Forward the click to the WidgetInteraction +void UVRWidgetInteractionComponent::OnBeginClick(const FInputActionValue& Value) +{ + PressPointerKey(EKeys::LeftMouseButton); +} + +// Forward the end click to the WidgetInteraction +void UVRWidgetInteractionComponent::OnEndClick(const FInputActionValue& Value) +{ + ReleasePointerKey(EKeys::LeftMouseButton); +} + +void UVRWidgetInteractionComponent::SetupInteractionRay() +{ + // Only create a new static mesh component if we haven't gotten one already + if (!InteractionRay) + { + // Create the new component + InteractionRay = NewObject<UStaticMeshComponent>(this); + + // If we're not already attached, attach. Not sure why this is needed, taken from engine code. + if (!InteractionRay->GetAttachParent() && !InteractionRay->IsAttachedTo(this)) + { + const AActor* Owner = GetOwner(); + + // Copied from engine code - I don't think this case will ever happen + if (!Owner || !Owner->GetWorld()) + { + if (UWorld* World = GetWorld()) + { + InteractionRay->RegisterComponentWithWorld(World); + InteractionRay->AttachToComponent(this, FAttachmentTransformRules::SnapToTargetNotIncludingScale); + } + else + { + InteractionRay->SetupAttachment(this); + } + } + else + { + // Usual case, just attach it to ourselves. + InteractionRay->AttachToComponent(this, FAttachmentTransformRules::SnapToTargetNotIncludingScale); + InteractionRay->RegisterComponent(); + } + } + } + + if (InteractionRay) + { + // Setup the interaction ray. + // The static mesh reference is set in the blueprint, avoid ConstructorHelpers and hardcoded paths! + // this ray model has an inlayed cross with flipped normals so it can be seen as a cross in desktop mode + // where the right hand is attached to the head + + if (InteractionRayMesh) + { + InteractionRay->SetStaticMesh(InteractionRayMesh); + } + + InteractionRay->SetCastShadow(false); + // turns off collisions as the InteractionRay is only meant to visualize the ray + InteractionRay->SetCollisionProfileName(TEXT("NoCollision")); + + //the ray model has a length of 100cm (and is a bit too big in Y/Z dir) + InteractionRay->SetRelativeScale3D(FVector(InteractionDistance / 100.0f, 0.5f, 0.5f)); + SetInteractionRayVisibility(InteractionRayVisibility); + } +} diff --git a/Source/RWTHVRToolkit/Private/Pawn/BasicVRInteractionComponent.cpp b/Source/RWTHVRToolkit/Private/Pawn/BasicVRInteractionComponent.cpp deleted file mode 100644 index 257313fc50e261ee9079ab1c1c2d632894d0100c..0000000000000000000000000000000000000000 --- a/Source/RWTHVRToolkit/Private/Pawn/BasicVRInteractionComponent.cpp +++ /dev/null @@ -1,175 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - - -#include "Pawn/BasicVRInteractionComponent.h" - -#include "Misc/Optional.h" - -DEFINE_LOG_CATEGORY(LogVRInteractionComponent); - -// Sets default values for this component's properties -UBasicVRInteractionComponent::UBasicVRInteractionComponent() -{ - // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features - // off to improve performance if you don't need them. - PrimaryComponentTick.bCanEverTick = true; - - // Setup the interaction ray. - InteractionRay = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Interaction Ray")); - InteractionRay->SetCastShadow(false); - // turns off collisions as the InteractionRay is only meant to visualize the ray - InteractionRay->SetCollisionProfileName(TEXT("NoCollision")); - // The static mesh reference is set in the blueprint, avoid ConstructorHelpers and hardcoded paths! - // this ray model has an inlayed cross with flipped normals so it can be seen as a cross in desktop mode where the right hand is attached to the head - - bShowDebug = false; //otherwise the WidgetInteractionComponent debug vis is shown - InteractionSource = EWidgetInteractionSource::Custom; - //can also be kept at default (World), this way, however, we efficiently reuse the line traces -} - -void UBasicVRInteractionComponent::BeginPlay() -{ - Super::BeginPlay(); - - //WidgetInteractionComponent - InteractionDistance = MaxClickDistance; - InteractionRay->SetRelativeScale3D(FVector(MaxClickDistance / 100.0f, 0.5f, 0.5f)); - //the ray model has a length of 100cm (and is a bit too big in Y/Z dir) - SetInteractionRayVisibility(InteractionRayVisibility); -} - -void UBasicVRInteractionComponent::BeginInteraction() -{ - if (!InteractionRayEmitter) return; - - // start and end point for raytracing - const FTwoVectors StartEnd = GetHandRay(MaxClickDistance); - TOptional<FHitResult> Hit = RaytraceForFirstHit(StartEnd); - if (!Hit.IsSet()) - return; - - AActor* HitActor = Hit->GetActor(); - - //trigger interaction of WidgetInteractionComponent - SetCustomHitResult(Hit.GetValue()); - //if !bCanRaytraceEveryTick, you have to click twice, since the first tick it only highlights and can't directly click - PressPointerKey(EKeys::LeftMouseButton); -} - -void UBasicVRInteractionComponent::EndInteraction() -{ - if (!InteractionRayEmitter) return; - - //end interaction of WidgetInteractionComponent - ReleasePointerKey(EKeys::LeftMouseButton); -} - -// Called every frame -void UBasicVRInteractionComponent::TickComponent(float DeltaTime, ELevelTick TickType, - FActorComponentTickFunction* ThisTickFunction) -{ - Super::TickComponent(DeltaTime, TickType, ThisTickFunction); - - if (!InteractionRayEmitter) return; - - // only raytrace for targetable objects if bool user wants to enable this feature - if (!bCanRaytraceEveryTick) - return; - - const FTwoVectors StartEnd = GetHandRay(MaxClickDistance); - TOptional<FHitResult> Hit = RaytraceForFirstHit(StartEnd); - - if (!Hit.IsSet() || !Hit->GetActor()) - { - if (InteractionRayVisibility == EInteractionRayVisibility::VisibleOnHoverOnly) - { - InteractionRay->SetVisibility(false); - } - return; - } - - AActor* HitActor = Hit->GetActor(); - - // widget interaction - SetCustomHitResult(Hit.GetValue()); - if (InteractionRayVisibility == EInteractionRayVisibility::VisibleOnHoverOnly) - { - if (IsOverInteractableWidget()) - { - InteractionRay->SetVisibility(true); - } - else - { - InteractionRay->SetVisibility(false); - } - } - LastActorHit = HitActor; // Store the actor that was hit to have access to it in the next frame as well -} - -void UBasicVRInteractionComponent::Initialize(USceneComponent* RayEmitter) -{ - if (InteractionRayEmitter) return; /* Return if already initialized */ - - InteractionRayEmitter = RayEmitter; - - InteractionRay->AttachToComponent(RayEmitter, FAttachmentTransformRules::KeepRelativeTransform); - this->AttachToComponent(RayEmitter, FAttachmentTransformRules::KeepRelativeTransform); -} - -void UBasicVRInteractionComponent::SetInteractionRayVisibility(EInteractionRayVisibility NewVisibility) -{ - InteractionRayVisibility = NewVisibility; - if (InteractionRay) - { - switch (InteractionRayVisibility) - { - case Visible: - InteractionRay->SetVisibility(true); - break; - case VisibleOnHoverOnly: - case Invisible: - InteractionRay->SetVisibility(false); - break; - default: ; - } - } - - if (InteractionRayVisibility == EInteractionRayVisibility::VisibleOnHoverOnly && !bCanRaytraceEveryTick) - { - UE_LOG(LogVRInteractionComponent, Warning, - TEXT("VisibleOnHoverOnly needs bCanRaytraceEveryTick=true, so this is set!")); - bCanRaytraceEveryTick = true; - } - if (InteractionRayVisibility == EInteractionRayVisibility::Visible && !bCanRaytraceEveryTick) - { - UE_LOG(LogVRInteractionComponent, Warning, - TEXT( - "VisibleOnHoverOnly will need two clicks to interact with widgets if bCanRaytraceEveryTick is not set!" - )); - } -} - -FTwoVectors UBasicVRInteractionComponent::GetHandRay(const float Length) const -{ - const FVector Start = InteractionRayEmitter->GetComponentLocation(); - const FVector Direction = InteractionRayEmitter->GetForwardVector(); - const FVector End = Start + Length * Direction; - - return FTwoVectors(Start, End); -} - -TOptional<FHitResult> UBasicVRInteractionComponent::RaytraceForFirstHit(const FTwoVectors& Ray) const -{ - const FVector Start = Ray.v1; - const FVector End = Ray.v2; - - // will be filled by the Line Trace Function - FHitResult Hit; - - FCollisionQueryParams Params; - Params.AddIgnoredActor(GetOwner()->GetUniqueID()); // prevents actor hitting itself - if (GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECollisionChannel::ECC_Visibility, Params)) - return {Hit}; - else - return {}; -} diff --git a/Source/RWTHVRToolkit/Private/Pawn/VirtualRealityPawn.cpp b/Source/RWTHVRToolkit/Private/Pawn/VirtualRealityPawn.cpp index 5d52b65f05e21211e2fd647101813d1b5b6bd78b..28309ec37d32fd6759763f1d32e498efd88159c9 100644 --- a/Source/RWTHVRToolkit/Private/Pawn/VirtualRealityPawn.cpp +++ b/Source/RWTHVRToolkit/Private/Pawn/VirtualRealityPawn.cpp @@ -36,9 +36,6 @@ AVirtualRealityPawn::AVirtualRealityPawn(const FObjectInitializer& ObjectInitial LeftHand = CreateDefaultSubobject<UReplicatedMotionControllerComponent>(TEXT("Left Hand MCC")); LeftHand->SetupAttachment(RootComponent); - - BasicVRInteraction = CreateDefaultSubobject<UBasicVRInteractionComponent>(TEXT("Basic VR Interaction")); - BasicVRInteraction->Initialize(RightHand); } void AVirtualRealityPawn::Tick(float DeltaSeconds) @@ -121,7 +118,7 @@ void AVirtualRealityPawn::SetupPlayerInputComponent(UInputComponent* PlayerInput PlayerController->bEnableClickEvents = true; PlayerController->bEnableMouseOverEvents = true; } - + // Set up mappings on input extension components, need to do this nicely for (UActorComponent* Comp : GetComponentsByInterface(UInputExtensionInterface::StaticClass())) diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactors/VRWidgetInteractionComponent.h b/Source/RWTHVRToolkit/Public/Interaction/Interactors/VRWidgetInteractionComponent.h new file mode 100644 index 0000000000000000000000000000000000000000..4228f9cd585bcdf6d02258138e9b69c9865b65de --- /dev/null +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactors/VRWidgetInteractionComponent.h @@ -0,0 +1,65 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/WidgetInteractionComponent.h" +#include "Pawn/InputExtensionInterface.h" +#include "VRWidgetInteractionComponent.generated.h" + +UENUM() +enum EInteractionRayVisibility +{ + Visible UMETA(DisplayName = "Interaction ray visible"), + VisibleOnHoverOnly UMETA( + DisplayName = + "Interaction ray only visible when hovering over Clickable or Targetable objects, or interactable widgets"), + Invisible UMETA(DisplayName = "Interaction ray invisible") +}; + + +UCLASS(Blueprintable, Abstract, ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) +class RWTHVRTOOLKIT_API UVRWidgetInteractionComponent : public UWidgetInteractionComponent, + public IInputExtensionInterface +{ + GENERATED_BODY() + +public: + UVRWidgetInteractionComponent(); + + virtual void SetupPlayerInput(UInputComponent* PlayerInputComponent) override; + + virtual void TickComponent(float DeltaTime, ELevelTick TickType, + FActorComponentTickFunction* ThisTickFunction) override; + + UFUNCTION(BlueprintCallable) + void SetInteractionRayVisibility(EInteractionRayVisibility NewVisibility); + + UPROPERTY(EditAnywhere) + UStaticMesh* InteractionRayMesh; + + UPROPERTY(BlueprintReadOnly) + UStaticMeshComponent* InteractionRay; + + // Enable this if you want to interact with Targetable classes or use EInteractionRayVisibility::VisibleOnHoverOnly + UPROPERTY(EditAnywhere) + bool bCanRaytraceEveryTick = false; + + UPROPERTY(EditAnywhere) + TEnumAsByte<EInteractionRayVisibility> InteractionRayVisibility = EInteractionRayVisibility::Invisible; + + UPROPERTY(EditDefaultsOnly, Category = "Input") + class UInputMappingContext* IMCWidgetInteraction; + + UPROPERTY(EditAnywhere, Category = "Input") + class UInputAction* WidgetClickInputAction; + +private: + UFUNCTION() + void OnBeginClick(const FInputActionValue& Value); + + UFUNCTION() + void OnEndClick(const FInputActionValue& Value); + + void SetupInteractionRay(); +}; diff --git a/Source/RWTHVRToolkit/Public/Pawn/BasicVRInteractionComponent.h b/Source/RWTHVRToolkit/Public/Pawn/BasicVRInteractionComponent.h deleted file mode 100644 index aa3ed916484868ae048784d478592f7116683089..0000000000000000000000000000000000000000 --- a/Source/RWTHVRToolkit/Public/Pawn/BasicVRInteractionComponent.h +++ /dev/null @@ -1,90 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "Components/WidgetInteractionComponent.h" -#include "BasicVRInteractionComponent.generated.h" - -DECLARE_LOG_CATEGORY_EXTERN(LogVRInteractionComponent, Log, All); - -class UGrabbingBehaviorComponent; - -UENUM() -enum EInteractionRayVisibility -{ - Visible UMETA(DisplayName = "Interaction ray visible"), - VisibleOnHoverOnly UMETA( - DisplayName = - "Interaction ray only visible when hovering over Clickable or Targetable objects, or interactable widgets"), - Invisible UMETA(DisplayName = "Interaction ray invisible") -}; - - -UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) -class RWTHVRTOOLKIT_API UBasicVRInteractionComponent : public UWidgetInteractionComponent -{ - GENERATED_BODY() - -public: - // Sets default values for this component's properties - UBasicVRInteractionComponent(); - - virtual void BeginPlay() override; - - virtual void TickComponent(float DeltaTime, ELevelTick TickType, - FActorComponentTickFunction* ThisTickFunction) override; - - UFUNCTION(BlueprintCallable) - void BeginInteraction(); - - UFUNCTION(BlueprintCallable) - void EndInteraction(); - - UFUNCTION(BlueprintCallable) - void Initialize(USceneComponent* RayEmitter); - - UFUNCTION(BlueprintCallable, BlueprintPure) - AActor* GetGrabbedActor() const { return GrabbedActor; } - - UFUNCTION(BlueprintCallable, BlueprintPure) - USceneComponent* GetInteractionRayEmitter() const { return InteractionRayEmitter; } - - UFUNCTION(BlueprintCallable) - void SetInteractionRayVisibility(EInteractionRayVisibility NewVisibility); - - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pawn|Interaction") - UStaticMeshComponent* InteractionRay; - - UPROPERTY(EditAnywhere, BlueprintReadWrite) - float MaxGrabDistance = 50; - - UPROPERTY(EditAnywhere, BlueprintReadWrite) - float MaxClickDistance = 500; - - // Enable this if you want to interact with Targetable classes or use EInteractionRayVisibility::VisibleOnHoverOnly - UPROPERTY(EditAnywhere) - bool bCanRaytraceEveryTick = false; - - UPROPERTY(EditAnywhere) - TEnumAsByte<EInteractionRayVisibility> InteractionRayVisibility = EInteractionRayVisibility::Invisible; - -private: - /* Holding a reference to the actor that is currently being grabbed */ - UPROPERTY() - AActor* GrabbedActor; - - /* Holds a reference to the grabbed actors physics simulating component if there was one*/ - UPROPERTY() - UPrimitiveComponent* ComponentSimulatingPhysics = nullptr; - - UPROPERTY() - USceneComponent* InteractionRayEmitter = nullptr; - - /* Stores the reference of the Actor that was hit in the last frame*/ - UPROPERTY() - AActor* LastActorHit = nullptr; - - FTwoVectors GetHandRay(float Length) const; - TOptional<FHitResult> RaytraceForFirstHit(const FTwoVectors& Ray) const; -}; diff --git a/Source/RWTHVRToolkit/Public/Pawn/VirtualRealityPawn.h b/Source/RWTHVRToolkit/Public/Pawn/VirtualRealityPawn.h index 5bc5dbc2bb16d65965ee84a452d2d820fc843613..53e7bda6ac7315dc05afa38c7395bf1fccdb195d 100644 --- a/Source/RWTHVRToolkit/Public/Pawn/VirtualRealityPawn.h +++ b/Source/RWTHVRToolkit/Public/Pawn/VirtualRealityPawn.h @@ -2,7 +2,6 @@ #pragma once -#include "BasicVRInteractionComponent.h" #include "CoreMinimal.h" #include "LiveLinkRole.h" #include "Pawn/Navigation/VRPawnMovement.h" @@ -35,10 +34,6 @@ public: UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pawn|MotionControllers") UMotionControllerComponent* LeftHand; - /* Interaction */ - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pawn|Interaction") - UBasicVRInteractionComponent* BasicVRInteraction; - /* Movement */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pawn|Movement") UVRPawnMovement* PawnMovement;