diff --git a/Content/PointingRay/Ray_Material.uasset b/Content/PointingRay/Ray_Material.uasset new file mode 100644 index 0000000000000000000000000000000000000000..4ae373bdf3e090fe996f4c48f4ccfc8ec9555b65 Binary files /dev/null and b/Content/PointingRay/Ray_Material.uasset differ diff --git a/Content/PointingRay/Ray_Mesh.uasset b/Content/PointingRay/Ray_Mesh.uasset new file mode 100644 index 0000000000000000000000000000000000000000..0da58a602c186aa1ac8d65ba57d2d966a78419b5 Binary files /dev/null and b/Content/PointingRay/Ray_Mesh.uasset differ diff --git a/Source/DisplayClusterExtensions/Private/Pawn/BasicVRInteractionComponent.cpp b/Source/DisplayClusterExtensions/Private/Pawn/BasicVRInteractionComponent.cpp index 0f3441165724e615f4fc7f2ca47416c29cf93c1b..bd2bc48ec7039890767e60ba9a3c618f7849198b 100644 --- a/Source/DisplayClusterExtensions/Private/Pawn/BasicVRInteractionComponent.cpp +++ b/Source/DisplayClusterExtensions/Private/Pawn/BasicVRInteractionComponent.cpp @@ -10,6 +10,9 @@ #include "Interaction/GrabbingBehaviorComponent.h" #include "Misc/Optional.h" #include "DrawDebugHelpers.h" +#include "Components/WidgetComponent.h" + +DEFINE_LOG_CATEGORY(LogVRInteractionComponent); // Sets default values for this component's properties UBasicVRInteractionComponent::UBasicVRInteractionComponent() @@ -18,7 +21,28 @@ UBasicVRInteractionComponent::UBasicVRInteractionComponent() // off to improve performance if you don't need them. PrimaryComponentTick.bCanEverTick = true; - // ... + // Setup the interaction ray. + InteractionRay = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Interaction Ray")); + //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 + ConstructorHelpers::FObjectFinder<UStaticMesh> MeshAsset(TEXT("/nDisplayExtensions/PointingRay/Ray_Mesh")); + if (MeshAsset.Object != nullptr) + { + InteractionRay->SetStaticMesh(MeshAsset.Object); + } + // turns off collisions as the InteractionRay is only meant to visualize the ray + InteractionRay->SetCollisionProfileName(TEXT("NoCollision")); + 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; + SetInteractionRayVisibility(InteractionRayVisibility); } void UBasicVRInteractionComponent::BeginInteraction() @@ -32,6 +56,12 @@ void UBasicVRInteractionComponent::BeginInteraction() 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); + if (HitActor->Implements<UGrabable>() && Hit->Distance < MaxGrabDistance) { @@ -55,6 +85,9 @@ void UBasicVRInteractionComponent::BeginInteraction() void UBasicVRInteractionComponent::EndInteraction() { if(!InteractionRayEmitter) return; + + //end interaction of WidgetInteractionComponent + ReleasePointerKey(EKeys::LeftMouseButton); // if we didnt grab anyone there is no need to release if (GrabbedActor == nullptr) @@ -107,6 +140,10 @@ void UBasicVRInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tic if (!Hit.IsSet()) { + if(InteractionRayVisibility==EInteractionRayVisibility::VisibleOnHoverOnly) + { + InteractionRay->SetVisibility(false); + } // Execute leave event on the actor that lost the focus if there was one if (LastActorHit && LastActorHit->Implements<UTargetable>()) @@ -141,6 +178,20 @@ void UBasicVRInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tic { ITargetable::Execute_OnTargeted(HitActor, Hit->Location); } + + // widget interaction + SetCustomHitResult(Hit.GetValue()); + if(InteractionRayVisibility==EInteractionRayVisibility::VisibleOnHoverOnly) + { + if(HitActor->Implements<UTargetable>() || HitActor->Implements<UClickable>() || 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 } @@ -151,6 +202,38 @@ void UBasicVRInteractionComponent::Initialize(USceneComponent* RayEmitter, float InteractionRayEmitter = RayEmitter; MaxGrabDistance = InMaxGrabDistance; MaxClickDistance = InMaxClickDistance; + + InteractionRay->AttachToComponent(RayEmitter, FAttachmentTransformRules::KeepRelativeTransform); + InteractionRay->SetRelativeScale3D(FVector(MaxClickDistance/100.0f, 1.0f, 1.0f)); //the ray model has a length of 100cm + 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; + } + } + + 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!")); + } } void UBasicVRInteractionComponent::HandlePhysicsAndAttachActor(AActor* HitActor) @@ -185,10 +268,9 @@ TOptional<FHitResult> UBasicVRInteractionComponent::RaytraceForFirstHit(const FT // will be filled by the Line Trace Function FHitResult Hit; - const FCollisionObjectQueryParams Params; - FCollisionQueryParams Params2; - Params2.AddIgnoredActor(GetOwner()->GetUniqueID()); // prevents actor hitting itself - if (GetWorld()->LineTraceSingleByObjectType(Hit, Start, End, Params, Params2)) + 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/DisplayClusterExtensions/Public/Pawn/BasicVRInteractionComponent.h b/Source/DisplayClusterExtensions/Public/Pawn/BasicVRInteractionComponent.h index 92bb238716cc9f86366c4a9fd8bf90c76b9a556e..b096d5f74a1583dee359d4ba1c607fed97463e79 100644 --- a/Source/DisplayClusterExtensions/Public/Pawn/BasicVRInteractionComponent.h +++ b/Source/DisplayClusterExtensions/Public/Pawn/BasicVRInteractionComponent.h @@ -3,14 +3,24 @@ #pragma once #include "CoreMinimal.h" -#include "Components/ActorComponent.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 DISPLAYCLUSTEREXTENSIONS_API UBasicVRInteractionComponent : public UActorComponent +class DISPLAYCLUSTEREXTENSIONS_API UBasicVRInteractionComponent : public UWidgetInteractionComponent { GENERATED_BODY() @@ -18,6 +28,8 @@ public: // Sets default values for this component's properties UBasicVRInteractionComponent(); + void BeginPlay() override; + UFUNCTION(BlueprintCallable) void BeginInteraction(); UFUNCTION(BlueprintCallable) void EndInteraction(); @@ -26,13 +38,16 @@ public: UPROPERTY(BlueprintReadWrite) float MaxGrabDistance = 50; UPROPERTY(BlueprintReadWrite) float MaxClickDistance = 500; - // Enable this if you want to interact with Targetable classes + // 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; UFUNCTION(BlueprintCallable) void Initialize(USceneComponent* RayEmitter, float InMaxGrabDistance = 50, float InMaxClickDistance = 500); UFUNCTION(BlueprintCallable, BlueprintPure) AActor* GetGrabbedActor() const { return GrabbedActor;} UFUNCTION(BlueprintCallable, BlueprintPure) USceneComponent* GetInteractionRayEmitter() const { return InteractionRayEmitter; } + + UFUNCTION(BlueprintCallable) void SetInteractionRayVisibility(EInteractionRayVisibility NewVisibility); private: /* Holding a reference to the actor that is currently being grabbed */ UPROPERTY() AActor* GrabbedActor; @@ -40,6 +55,8 @@ private: UPROPERTY() UPrimitiveComponent* ComponentSimulatingPhysics = nullptr; UPROPERTY() UGrabbingBehaviorComponent* Behavior = nullptr; UPROPERTY() USceneComponent* InteractionRayEmitter = nullptr; + UPROPERTY() UStaticMeshComponent* InteractionRay = nullptr; + /* Stores the reference of the Actor that was hit in the last frame*/ UPROPERTY() AActor* LastActorHit = nullptr; void HandlePhysicsAndAttachActor(AActor* HitActor);