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..8a35d2c88da8bde64b6e2fcff085658ec8561964 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 c0a06d49cb89fb87288b1246293ce0335f26764d..fe546cd44482d92ac317eba6d30ba68a60a3598b 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) @@ -105,7 +138,13 @@ void UBasicVRInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tic const FTwoVectors StartEnd = GetHandRay(MaxClickDistance); TOptional<FHitResult> Hit = RaytraceForFirstHit(StartEnd); if (!Hit.IsSet()) + { + if(InteractionRayVisibility==EInteractionRayVisibility::VisibleOnHoverOnly) + { + InteractionRay->SetVisibility(false); + } return; + } AActor* HitActor = Hit->GetActor(); // for now uses the same distance as clicking @@ -113,6 +152,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); + } + } } void UBasicVRInteractionComponent::Initialize(USceneComponent* RayEmitter, float InMaxGrabDistance, float InMaxClickDistance) @@ -122,6 +175,37 @@ void UBasicVRInteractionComponent::Initialize(USceneComponent* RayEmitter, float InteractionRayEmitter = RayEmitter; MaxGrabDistance = InMaxGrabDistance; MaxClickDistance = InMaxClickDistance; + + 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; + } + } + + 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) @@ -156,10 +240,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 6e86c208e971ad141a34c489bc1d6d3aca123d5c..29ec7c9acaa169ff9017067eace54983470519be 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, + VisibleOnHoverOnly, + 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 - UPROPERTY(EditAnywhere) bool bCanRaytraceEveryTick = false; + // Enable this if you want to interact with Targetable classes or use EInteractionRayVisibility::VisibleOnHoverOnly + UPROPERTY(EditAnywhere) bool bCanRaytraceEveryTick = true; + UPROPERTY(EditAnywhere) TEnumAsByte<EInteractionRayVisibility> InteractionRayVisibility = EInteractionRayVisibility::VisibleOnHoverOnly; 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,7 @@ private: UPROPERTY() UPrimitiveComponent* ComponentSimulatingPhysics = nullptr; UPROPERTY() UGrabbingBehaviorComponent* Behavior = nullptr; UPROPERTY() USceneComponent* InteractionRayEmitter = nullptr; + UPROPERTY() UStaticMeshComponent* InteractionRay = nullptr; void HandlePhysicsAndAttachActor(AActor* HitActor); FTwoVectors GetHandRay(float Length) const;