diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8ce48654ef858bfa14c891f216af48eb3820eb05..93d693e418fdde59a75556ec96d65e03313e3293 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -42,7 +42,7 @@ include: variables: UNREAL_VERSION: "5.4" - CUSTOM_NDISPLAY_CONFIG: "aixcave_5_3_dev.ndisplay" + CUSTOM_NDISPLAY_CONFIG: "aixcave_5_4.ndisplay" stages: - analyze @@ -83,7 +83,7 @@ Generate_Project: RUN_SETUP: "false" GEN_DEPENDENCIES: "( [master@UnrealDTrackPlugin]='https://github.com/VRGroupRWTH/UnrealDTrackPlugin.git' - [dev/5.3@RWTHVRCluster]='https://git-ce.rwth-aachen.de/vr-vis/VR-Group/unreal-development/plugins/rwth-vr-cluster-plugin.git' + [dev/5.4@RWTHVRCluster]='https://git-ce.rwth-aachen.de/vr-vis/VR-Group/unreal-development/plugins/rwth-vr-cluster-plugin.git' )" Generate_Project_Without_Cluster: diff --git a/Config/DefaultRWTHVRToolkit.ini b/Config/DefaultRWTHVRToolkit.ini index 13066ebe26e8ab454486f97bbb982275d9b2587c..5e0f330705848d2342828dce06c75fa670d760d4 100644 --- a/Config/DefaultRWTHVRToolkit.ini +++ b/Config/DefaultRWTHVRToolkit.ini @@ -18,4 +18,15 @@ +FunctionRedirects = (OldName="/Script/RWTHVRToolkit.DirectInteractionComponent.OnBeginGrab",NewName="/Script/RWTHVRToolkit.DirectInteractionComponent.OnBeginInteraction") +FunctionRedirects = (OldName="/Script/RWTHVRToolkit.DirectInteractionComponent.OnEndGrab",NewName="/Script/RWTHVRToolkit.DirectInteractionComponent.OnEndInteraction") +PropertyRedirects = (OldName="/Script/RWTHVRToolkit.DirectInteractionComponent.PreviousGrabBehavioursInRange",NewName="/Script/RWTHVRToolkit.DirectInteractionComponent.PreviousInteractableComponentsInRange") -+PropertyRedirects = (OldName="/Script/RWTHVRToolkit.DirectInteractionComponent.CurrentGrabBehavioursInRange",NewName="/Script/RWTHVRToolkit.DirectInteractionComponent.CurrentInteractableComponentsInRange") \ No newline at end of file ++PropertyRedirects = (OldName="/Script/RWTHVRToolkit.DirectInteractionComponent.CurrentGrabBehavioursInRange",NewName="/Script/RWTHVRToolkit.DirectInteractionComponent.CurrentInteractableComponentsInRange") ++PropertyRedirects = (OldName="/Script/RWTHVRToolkit.TurnComponent.DesktopRotation",NewName="/Script/RWTHVRToolkit.TurnComponent.DesktopTurnCondition") ++PropertyRedirects = (OldName="/Script/RWTHVRToolkit.TurnComponent.Turn",NewName="/Script/RWTHVRToolkit.TurnComponent.XRTurn") ++PropertyRedirects = (OldName="/Script/RWTHVRToolkit.RWTHVRWidgetInteractionComponent.WidgetClickInputAction",NewName="/Script/RWTHVRToolkit.RWTHVRWidgetInteractionComponent.WidgetLeftClickInputAction") ++FunctionRedirects = (OldName="/Script/RWTHVRToolkit.RWTHVRWidgetInteractionComponent.OnBeginClick",NewName="/Script/RWTHVRToolkit.RWTHVRWidgetInteractionComponent.OnBeginLeftClick") ++FunctionRedirects = (OldName="/Script/RWTHVRToolkit.RWTHVRWidgetInteractionComponent.OnEndClick",NewName="/Script/RWTHVRToolkit.RWTHVRWidgetInteractionComponent.OnEndLeftClick") ++FunctionRedirects=(OldName="/Script/RWTHVRToolkit.UBaseInteractionComponent.OnBeginInteraction",NewName="/Script/RWTHVRToolkit.UBaseInteractionComponent.OnBeginInteractionInputAction") ++FunctionRedirects=(OldName="/Script/RWTHVRToolkit.UBaseInteractionComponent.OnEndInteraction",NewName="/Script/RWTHVRToolkit.UBaseInteractionComponent.OnEndInteractionInputAction") ++FunctionRedirects=(OldName="/Script/RWTHVRToolkit.UBaseInteractionComponent.MulticastHoverBehaviourStartRpc",NewName="/Script/RWTHVRToolkit.UBaseInteractionComponent.MulticastHoverBehaviourReplicationStartRpc") ++FunctionRedirects=(OldName="/Script/RWTHVRToolkit.UBaseInteractionComponent.MulticastActionBehaviourStartRpc",NewName="/Script/RWTHVRToolkit.UBaseInteractionComponent.MulticastActionBehaviourReplicationStartRpc") ++FunctionRedirects=(OldName="/Script/RWTHVRToolkit.ActionBehaviour.OnActionStart",NewName="/Script/RWTHVRToolkit.ActionBehaviour.OnActionEvent") ++PropertyRedirects=(OldName="/Script/RWTHVRToolkit.ActionBehaviour.OnActionBeginEvent",NewName="/Script/RWTHVRToolkit.ActionBehaviour.OnActionEventEvent") \ No newline at end of file diff --git a/Content/BP_RWTHVRGameModeBase.uasset b/Content/BP_RWTHVRGameModeBase.uasset index a073a7f7cc247654e724c9d348d57f9bdc55d9ed..3bfda91bae7068c7283ece3e8bbe26477c25bd90 100644 Binary files a/Content/BP_RWTHVRGameModeBase.uasset and b/Content/BP_RWTHVRGameModeBase.uasset differ diff --git a/Content/Components/Movement/Turn/BP_TurnComponent.uasset b/Content/Components/Movement/Turn/BP_TurnComponent.uasset index a572594744827a04a0a6a6b1912e80ef9be86297..d45f880d4277580f76c4fc943d97c35c7a3d0386 100644 Binary files a/Content/Components/Movement/Turn/BP_TurnComponent.uasset and b/Content/Components/Movement/Turn/BP_TurnComponent.uasset differ diff --git a/Content/Components/Movement/Turn/IA_DesktopRotation.uasset b/Content/Components/Movement/Turn/IA_DesktopRotation.uasset deleted file mode 100644 index 9a780d7a9679fdf2a68a1fca575200140e057a73..0000000000000000000000000000000000000000 Binary files a/Content/Components/Movement/Turn/IA_DesktopRotation.uasset and /dev/null differ diff --git a/Content/Components/Movement/Turn/IA_DesktopTurn.uasset b/Content/Components/Movement/Turn/IA_DesktopTurn.uasset new file mode 100644 index 0000000000000000000000000000000000000000..db8e3f16401d6456abf4e8d1476bde839e8c39b1 Binary files /dev/null and b/Content/Components/Movement/Turn/IA_DesktopTurn.uasset differ diff --git a/Content/Components/Movement/Turn/IA_DesktopTurnCondition.uasset b/Content/Components/Movement/Turn/IA_DesktopTurnCondition.uasset new file mode 100644 index 0000000000000000000000000000000000000000..bdf5a71cce576dd790298eb478b217e6968d2739 Binary files /dev/null and b/Content/Components/Movement/Turn/IA_DesktopTurnCondition.uasset differ diff --git a/Content/Components/WidgetInteraction/BP_RWTHVRWidgetInteractionComponent.uasset b/Content/Components/WidgetInteraction/BP_RWTHVRWidgetInteractionComponent.uasset index 904f5d3363442da0ff916b54041f595a4e023ffc..f6f9bafd520036d434be5501aade85a17a8a6f7a 100644 Binary files a/Content/Components/WidgetInteraction/BP_RWTHVRWidgetInteractionComponent.uasset and b/Content/Components/WidgetInteraction/BP_RWTHVRWidgetInteractionComponent.uasset differ diff --git a/Content/Components/WidgetInteraction/IA_WidgetClick.uasset b/Content/Components/WidgetInteraction/IA_WidgetClick.uasset deleted file mode 100644 index 470e02e6edeec5b128dd66f602fc5680e3fb9dbe..0000000000000000000000000000000000000000 Binary files a/Content/Components/WidgetInteraction/IA_WidgetClick.uasset and /dev/null differ diff --git a/Content/Components/WidgetInteraction/IA_WidgetLeftClick.uasset b/Content/Components/WidgetInteraction/IA_WidgetLeftClick.uasset new file mode 100644 index 0000000000000000000000000000000000000000..c3e97377bcc821f4ea99df5a92b44594c710564f Binary files /dev/null and b/Content/Components/WidgetInteraction/IA_WidgetLeftClick.uasset differ diff --git a/Content/Components/WidgetInteraction/IA_WidgetRightClick.uasset b/Content/Components/WidgetInteraction/IA_WidgetRightClick.uasset new file mode 100644 index 0000000000000000000000000000000000000000..320bf66bc73fa8714baae5258c680aba5f20261a Binary files /dev/null and b/Content/Components/WidgetInteraction/IA_WidgetRightClick.uasset differ diff --git a/Content/Input/Default_IMC/IMC_General.uasset b/Content/Input/Default_IMC/IMC_General.uasset index 6acdb72a1f1896d89873376ff2a06bdd4788506a..bfc100b8d7b66753e20343576c91f76dc51cfaac 100644 Binary files a/Content/Input/Default_IMC/IMC_General.uasset and b/Content/Input/Default_IMC/IMC_General.uasset differ diff --git a/Content/Input/Default_IMC/IMC_MovementLeftHand.uasset b/Content/Input/Default_IMC/IMC_MovementLeftHand.uasset index 61a3048f2f7d7470bcc88dd398b06a5cbd825939..0aed5008caf02d89df79d46b4267a447a5e0d9cb 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 fd7e3cd5dd01591ed98c06be64b54ca17221f8c0..153723ca229bab41ed167983d4751d2d7312a5d6 100644 Binary files a/Content/Input/Default_IMC/IMC_MovementRightHand.uasset and b/Content/Input/Default_IMC/IMC_MovementRightHand.uasset differ diff --git a/Content/Pawn/BP_RWTHVRPawn_Default.uasset b/Content/Pawn/BP_RWTHVRPawn_Default.uasset index cb05e67f79561281c52e04b21e9bd67726871dbb..145ddf5f938d0a3612c7a609aafa75a30d82063f 100644 Binary files a/Content/Pawn/BP_RWTHVRPawn_Default.uasset and b/Content/Pawn/BP_RWTHVRPawn_Default.uasset differ diff --git a/Content/Pawn/Base/BP_RWTHVRPawn_Base.uasset b/Content/Pawn/Base/BP_RWTHVRPawn_Base.uasset index 3616819e69aa1ac0f21047b036be9244b1597c76..cba6c2e1875f45603f51e234dbb9b066017654f9 100644 Binary files a/Content/Pawn/Base/BP_RWTHVRPawn_Base.uasset and b/Content/Pawn/Base/BP_RWTHVRPawn_Base.uasset differ diff --git a/Source/RWTHVRToolkit/Private/Core/RWTHVRGameModeBase.cpp b/Source/RWTHVRToolkit/Private/Core/RWTHVRGameModeBase.cpp index 4d9b4b6e4dcdb1447e2d9ff98fbfc5400f000589..9102b16012edc3c0c211e77fa3ec9e7f1b4f9520 100644 --- a/Source/RWTHVRToolkit/Private/Core/RWTHVRGameModeBase.cpp +++ b/Source/RWTHVRToolkit/Private/Core/RWTHVRGameModeBase.cpp @@ -7,9 +7,15 @@ #include "GameFramework/SpectatorPawn.h" #include "Kismet/GameplayStatics.h" #include "Logging/StructuredLog.h" +#include "Pawn/ClusterRepresentationActor.h" #include "Utility/RWTHVRUtilities.h" +ARWTHVRGameModeBase::ARWTHVRGameModeBase(const FObjectInitializer& ObjectInitializer) +{ + PlayerStateClass = ARWTHVRPlayerState::StaticClass(); +} + FString ARWTHVRGameModeBase::InitNewPlayer(APlayerController* NewPlayerController, const FUniqueNetIdRepl& UniqueId, const FString& Options, const FString& Portal) { @@ -17,6 +23,7 @@ FString ARWTHVRGameModeBase::InitNewPlayer(APlayerController* NewPlayerControlle // but I don't really want to introduce a hard dependency here. const FString NodeNameKey = "node"; const FString PrimaryNodeIdKey = "PrimaryNodeId"; + const FString ClusterIdKey = "ClusterId"; // Check if we're using our custom PlayerState so that we can save the player type there. // If not, just ingore all related args. @@ -24,6 +31,7 @@ FString ARWTHVRGameModeBase::InitNewPlayer(APlayerController* NewPlayerControlle if (State != nullptr) { + int32 ClusterId = -1; if (UGameplayStatics::HasOption(Options, PrimaryNodeIdKey)) { const FString PrimaryNodeId = UGameplayStatics::ParseOption(Options, PrimaryNodeIdKey); @@ -34,10 +42,22 @@ FString ARWTHVRGameModeBase::InitNewPlayer(APlayerController* NewPlayerControlle ? UGameplayStatics::ParseOption(Options, NodeNameKey) : PrimaryNodeId; + ClusterId = UGameplayStatics::HasOption(Options, ClusterIdKey) + ? FCString::Atoi(*UGameplayStatics::ParseOption(Options, ClusterIdKey)) + : -1; + const EPlayerType Type = NodeName == PrimaryNodeId ? EPlayerType::nDisplayPrimary : EPlayerType::nDisplaySecondary; State->RequestSetPlayerType(Type); } + else if (GetNetMode() == NM_Standalone && URWTHVRUtilities::IsRoomMountedMode()) + { + ClusterId = 0; + const EPlayerType Type = + URWTHVRUtilities::IsPrimaryNode() ? EPlayerType::nDisplayPrimary : EPlayerType::nDisplaySecondary; + State->RequestSetPlayerType(Type); + } + State->SetCorrespondingClusterId(ClusterId); } return Super::InitNewPlayer(NewPlayerController, UniqueId, Options, Portal); @@ -45,8 +65,47 @@ FString ARWTHVRGameModeBase::InitNewPlayer(APlayerController* NewPlayerControlle void ARWTHVRGameModeBase::PostLogin(APlayerController* NewPlayer) { - if (const ARWTHVRPlayerState* State = Cast<ARWTHVRPlayerState>(NewPlayer->PlayerState); State != nullptr) + if (ARWTHVRPlayerState* State = Cast<ARWTHVRPlayerState>(NewPlayer->PlayerState); State != nullptr) { + // If we're in none-standalone netmode, this is only executed on the server, as the GM only exists there. + // On standalone, this is executed on every node. + int32 ClusterId = State->GetCorrespondingClusterId(); + if (ClusterId >= 0) // we're either standalone (0) or in an acutal cluster + { + AClusterRepresentationActor** ClusterRepresentationPtr = ConnectedClusters.Find(ClusterId); + AClusterRepresentationActor* ClusterRepresentation; + if (!ClusterRepresentationPtr) + { + // No actor there yet, spawn it + FActorSpawnParameters SpawnParameters; + SpawnParameters.Name = FName(*FString::Printf(TEXT("ClusterRepresentation_%d"), ClusterId)); + SpawnParameters.NameMode = FActorSpawnParameters::ESpawnActorNameMode::Requested; + ClusterRepresentation = GetWorld()->SpawnActor<AClusterRepresentationActor>(SpawnParameters); + UE_LOGFMT(Toolkit, Display, + "ARWTHVRGameModeBase: Spawned ClusterRepresentationActor {Name} for Cluster {Id}", + ClusterRepresentation->GetName(), ClusterId); + ConnectedClusters.Add(ClusterId, ClusterRepresentation); + } + else + { + ClusterRepresentation = *ClusterRepresentationPtr; + } + + UE_LOGFMT(Toolkit, Display, "ARWTHVRGameModeBase: Using ClusterRepresentationActor {Name} for Cluster {Id}", + *ClusterRepresentation->GetName(), ClusterId); + + // Double check for sanity + check(ClusterRepresentation != nullptr); + + State->SetCorrespondingClusterActor(ClusterRepresentation); + + if (State->GetPlayerType() == EPlayerType::nDisplayPrimary) + { + // We're the owner of the actor! + ClusterRepresentation->SetOwner(NewPlayer); + } + } + // Do we already have an auto-possessing pawn possessed? if (NewPlayer->GetPawn() && NewPlayer->GetPawn()->IsValidLowLevelFast()) { @@ -56,10 +115,11 @@ void ARWTHVRGameModeBase::PostLogin(APlayerController* NewPlayer) return; } + // When we're not in standalone: // If the new player is a secondary nDisplay node, spawn it only as a Spectator // Potentially we can use MustSpectate instead. UClass* PawnClass; - if (State->GetPlayerType() == EPlayerType::nDisplaySecondary) + if (GetNetMode() != NM_Standalone && State->GetPlayerType() == EPlayerType::nDisplaySecondary) { // For now, simply use the BP approach of spawning the pawn here. Can do this in a better way potentially. PawnClass = SpectatorClass; @@ -83,11 +143,20 @@ void ARWTHVRGameModeBase::PostLogin(APlayerController* NewPlayer) } } + if (GetNetMode() == NM_Standalone) + { + const FName BaseName = PawnClass->HasAnyFlags(RF_ClassDefaultObject) + ? PawnClass->GetFName() + : *PawnClass->GetFName().GetPlainNameString(); + + SpawnInfo.Name = BaseName; + SpawnInfo.NameMode = FActorSpawnParameters::ESpawnActorNameMode::Requested; + } + // Spawn and possess the pawn APawn* ResultPawn = GetWorld()->SpawnActor<APawn>(PawnClass, StartSpot->GetTransform(), SpawnInfo); NewPlayer->Possess(ResultPawn); } - Super::PostLogin(NewPlayer); } diff --git a/Source/RWTHVRToolkit/Private/Core/RWTHVRPlayerState.cpp b/Source/RWTHVRToolkit/Private/Core/RWTHVRPlayerState.cpp index 0ad60f0c44a7853df65507cd23b9943c6cd8530b..4ae4795c85058331aeea4a2ff08df643b3cccd05 100644 --- a/Source/RWTHVRToolkit/Private/Core/RWTHVRPlayerState.cpp +++ b/Source/RWTHVRToolkit/Private/Core/RWTHVRPlayerState.cpp @@ -3,8 +3,10 @@ #include "Core/RWTHVRPlayerState.h" +#include "Logging/StructuredLog.h" #include "Net/UnrealNetwork.h" #include "Net/Core/PushModel/PushModel.h" +#include "Utility/RWTHVRUtilities.h" // Boilerplate, copies properties to new state void ARWTHVRPlayerState::CopyProperties(class APlayerState* PlayerState) @@ -17,6 +19,8 @@ void ARWTHVRPlayerState::CopyProperties(class APlayerState* PlayerState) if (IsValid(RWTHVRPlayerState)) { RWTHVRPlayerState->SetPlayerType(GetPlayerType()); + RWTHVRPlayerState->SetCorrespondingClusterId(CorrespondingClusterId); + RWTHVRPlayerState->SetCorrespondingClusterActor(CorrespondingClusterActor); } } } @@ -32,6 +36,8 @@ void ARWTHVRPlayerState::OverrideWith(class APlayerState* PlayerState) if (IsValid(RWTHVRPlayerState)) { SetPlayerType(RWTHVRPlayerState->GetPlayerType()); + SetCorrespondingClusterId(RWTHVRPlayerState->CorrespondingClusterId); + SetCorrespondingClusterActor(RWTHVRPlayerState->CorrespondingClusterActor); } } } @@ -45,6 +51,8 @@ void ARWTHVRPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& O SharedParams.bIsPushBased = true; DOREPLIFETIME_WITH_PARAMS_FAST(ARWTHVRPlayerState, PlayerType, SharedParams); + DOREPLIFETIME_WITH_PARAMS_FAST(ARWTHVRPlayerState, CorrespondingClusterId, SharedParams); + DOREPLIFETIME_WITH_PARAMS_FAST(ARWTHVRPlayerState, CorrespondingClusterActor, SharedParams); } void ARWTHVRPlayerState::ServerSetPlayerTypeRpc_Implementation(const EPlayerType NewPlayerType) @@ -57,6 +65,28 @@ void ARWTHVRPlayerState::SetPlayerType(const EPlayerType NewPlayerType) MARK_PROPERTY_DIRTY_FROM_NAME(ARWTHVRPlayerState, PlayerType, this); PlayerType = NewPlayerType; } +void ARWTHVRPlayerState::SetCorrespondingClusterId(int32 NewCorrespondingClusterId) +{ + if (!HasAuthority()) + { + UE_LOGFMT(Toolkit, Warning, "ARWTHVRPlayerState: Cannot set cluster Id on non-authority!"); + return; + } + + MARK_PROPERTY_DIRTY_FROM_NAME(ARWTHVRPlayerState, CorrespondingClusterId, this); + CorrespondingClusterId = NewCorrespondingClusterId; +} +void ARWTHVRPlayerState::SetCorrespondingClusterActor(AClusterRepresentationActor* NewCorrespondingClusterActor) +{ + if (!HasAuthority()) + { + UE_LOGFMT(Toolkit, Warning, "ARWTHVRPlayerState: Cannot set cluster actor ref on non-authority!"); + return; + } + + MARK_PROPERTY_DIRTY_FROM_NAME(ARWTHVRPlayerState, CorrespondingClusterActor, this); + CorrespondingClusterActor = NewCorrespondingClusterActor; +} void ARWTHVRPlayerState::RequestSetPlayerType(const EPlayerType NewPlayerType) { diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/ActionBehaviour.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/ActionBehaviour.cpp index 4e9c3fd662dd3baa2a30fddd8f516022e4efa9ba..ff1d027208025476ce073a15bd224ca5c9ccb9f0 100644 --- a/Source/RWTHVRToolkit/Private/Interaction/Interactables/ActionBehaviour.cpp +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/ActionBehaviour.cpp @@ -6,27 +6,14 @@ // We disable ticking here, as we are mainly interested in the events UActionBehaviour::UActionBehaviour() { PrimaryComponentTick.bCanEverTick = false; } -void UActionBehaviour::OnActionStart(USceneComponent* TriggeredComponent, const UInputAction* InputAction, +void UActionBehaviour::OnActionEvent(USceneComponent* TriggerComponent, const EInteractionEventType EventType, const FInputActionValue& Value) { } -void UActionBehaviour::OnActionEnd(USceneComponent* TriggeredComponent, const UInputAction* InputAction, - const FInputActionValue& Value) -{ -} - void UActionBehaviour::BeginPlay() { Super::BeginPlay(); - OnActionBeginEvent.AddDynamic(this, &UActionBehaviour::OnActionStart); - OnActionEndEvent.AddDynamic(this, &UActionBehaviour::OnActionEnd); -} - -// Called every frame -void UActionBehaviour::TickComponent(float DeltaTime, ELevelTick TickType, - FActorComponentTickFunction* ThisTickFunction) -{ - Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + OnActionEventEvent.AddDynamic(this, &UActionBehaviour::OnActionEvent); } diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/BaseBehaviour.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/BaseBehaviour.cpp new file mode 100644 index 0000000000000000000000000000000000000000..95488260668c095eb5dece6895555ce808e2fd11 --- /dev/null +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/BaseBehaviour.cpp @@ -0,0 +1,4 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Interaction/Interactables/BaseBehaviour.h" diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/GrabBehavior.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/GrabBehavior.cpp index 1537243da0b6c92a901decadce793bc8bd819bbb..502a4be532df57e6f3214e859db8eb4729b15482 100644 --- a/Source/RWTHVRToolkit/Private/Interaction/Interactables/GrabBehavior.cpp +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/GrabBehavior.cpp @@ -3,10 +3,26 @@ #include "Interaction/Interactables/GrabBehavior.h" #include "Interaction/Interactables/InteractableComponent.h" +#include "Interaction/Interactables/InteractionEventType.h" #include "Logging/StructuredLog.h" +#include "Pawn/Navigation/CollisionHandlingMovement.h" #include "Serialization/JsonTypes.h" #include "Utility/RWTHVRUtilities.h" +UGrabBehavior::UGrabBehavior() +{ + SetIsReplicatedByDefault(true); + bExecuteOnServer = true; + bExecuteOnAllClients = false; +} + +void UGrabBehavior::BeginPlay() +{ + Super::BeginPlay(); + + OnActionReplicationStartedOriginatorEvent.AddDynamic(this, &UGrabBehavior::ReplicationOriginaterClientCallback); +} + UPrimitiveComponent* UGrabBehavior::GetFirstComponentSimulatingPhysics(const AActor* TargetActor) { TArray<UPrimitiveComponent*> PrimitiveComponents; @@ -33,16 +49,79 @@ UPrimitiveComponent* UGrabBehavior::GetHighestParentSimulatingPhysics(UPrimitive return Comp; } +void UGrabBehavior::ReplicationOriginaterClientCallback(USceneComponent* TriggerComponent, + const EInteractionEventType EventType, + const FInputActionValue& Value) +{ + const USceneComponent* CurrentAttachParent = Cast<USceneComponent>(TriggerComponent->GetAttachParent()); + HandleCollisionHandlingMovement(CurrentAttachParent, EventType); +} -void UGrabBehavior::OnActionStart(USceneComponent* TriggeredComponent, const UInputAction* InputAction, +void UGrabBehavior::HandleCollisionHandlingMovement(const USceneComponent* CurrentAttachParent, + const EInteractionEventType EventType) +{ + auto CHM = CurrentAttachParent->GetOwner()->GetComponentByClass<UCollisionHandlingMovement>(); + if (!CHM) + return; + + if (EventType == EInteractionEventType::InteractionStart) + { + // Add to ignore list for collision handling movement, if it exists + if (bIgnoreGrabbedActorInCollisionMovement) + { + bWasAddedToIgnore = CHM->AddActorToIgnore(GetOwner()); + } + } + else + { + // If our attach parent has a collision handling component, remove + if (bWasAddedToIgnore) + { + CHM->RemoveActorFromIgnore(GetOwner()); + } + } +} +void UGrabBehavior::OnActionEvent(USceneComponent* TriggerComponent, const EInteractionEventType EventType, const FInputActionValue& Value) +{ + if (EventType == EInteractionEventType::InteractionStart) + { + StartGrab(TriggerComponent); + } + else + { + EndGrab(TriggerComponent); + } +} + +bool UGrabBehavior::TryRelease() +{ + if (!bObjectGrabbed) + { + UE_LOGFMT(Toolkit, Display, "UGrabBehavior::TryRelease: bObjectGrabbed was false!"); + return false; + } + + if (MyPhysicsComponent) + { + MyPhysicsComponent->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); + MyPhysicsComponent->SetSimulatePhysics(bWasSimulatingPhysics); + } + else + { + GetOwner()->GetRootComponent()->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); + } + bObjectGrabbed = false; + return true; +} +void UGrabBehavior::StartGrab(USceneComponent* TriggerComponent) { if (bObjectGrabbed) { return; } - USceneComponent* CurrentAttachParent = Cast<USceneComponent>(TriggeredComponent->GetAttachParent()); + USceneComponent* CurrentAttachParent = Cast<USceneComponent>(TriggerComponent->GetAttachParent()); const FAttachmentTransformRules Rules = FAttachmentTransformRules(EAttachmentRule::KeepWorld, false); if (MyPhysicsComponent = GetFirstComponentSimulatingPhysics(GetOwner()); MyPhysicsComponent != nullptr) @@ -72,18 +151,19 @@ void UGrabBehavior::OnActionStart(USceneComponent* TriggeredComponent, const UIn GetOwner()->GetComponents<UInteractableComponent>(Interactables, false); for (UInteractableComponent* Interactable : Interactables) { - Interactable->RestrictInteractionToComponent(TriggeredComponent); + Interactable->RestrictInteractionToComponent(TriggerComponent); } } OnGrabStartEvent.Broadcast(CurrentAttachParent, MyPhysicsComponent); + + // Add to ignore list for collision handling movement, if it exists + HandleCollisionHandlingMovement(CurrentAttachParent, InteractionStart); } -void UGrabBehavior::OnActionEnd(USceneComponent* TriggeredComponent, const UInputAction* InputAction, - const FInputActionValue& Value) +void UGrabBehavior::EndGrab(USceneComponent* TriggerComponent) { - - USceneComponent* CurrentAttachParent = Cast<USceneComponent>(TriggeredComponent->GetAttachParent()); + USceneComponent* CurrentAttachParent = Cast<USceneComponent>(TriggerComponent->GetAttachParent()); // We try to release the attached component. If it is not succesful we log and return. Otherwise, we continue. if (!TryRelease()) @@ -106,24 +186,7 @@ void UGrabBehavior::OnActionEnd(USceneComponent* TriggeredComponent, const UInpu Interactable->ResetRestrictInteraction(); } } -} -bool UGrabBehavior::TryRelease() -{ - if (!bObjectGrabbed) - { - return false; - } - - if (MyPhysicsComponent) - { - MyPhysicsComponent->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); - MyPhysicsComponent->SetSimulatePhysics(bWasSimulatingPhysics); - } - else - { - GetOwner()->GetRootComponent()->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); - } - bObjectGrabbed = false; - return true; + // If our attach parent has a collision handling component, remove + HandleCollisionHandlingMovement(CurrentAttachParent, InteractionEnd); } diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/HoverBehaviour.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/HoverBehaviour.cpp index 96897613cd2f536cb9625a9b395ce49181656e70..f78e75fabfa434b1396d7bffc1354adee7f49441 100644 --- a/Source/RWTHVRToolkit/Private/Interaction/Interactables/HoverBehaviour.cpp +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/HoverBehaviour.cpp @@ -3,14 +3,13 @@ #include "Interaction/Interactables/HoverBehaviour.h" -void UHoverBehaviour::OnHoverStart(const USceneComponent* TriggeredComponent, FHitResult Hit) {} - -void UHoverBehaviour::OnHoverEnd(const USceneComponent* TriggeredComponent) {} +void UHoverBehaviour::OnHoverEvent(const USceneComponent* TriggerComponent, EInteractionEventType EventType, + const FHitResult& Hit) +{ +} void UHoverBehaviour::BeginPlay() { Super::BeginPlay(); - - OnHoverStartEvent.AddDynamic(this, &UHoverBehaviour::OnHoverStart); - OnHoverEndEvent.AddDynamic(this, &UHoverBehaviour::OnHoverEnd); + OnHoverEventEvent.AddDynamic(this, &UHoverBehaviour::OnHoverEvent); } diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/IntenSelect/IntenSelectable.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/IntenSelect/IntenSelectable.cpp index 90a4e2956a3d06f1cb42d6219e4f7f1ff31ca050..276269b3dc0f389621e6415a71887cf3170a9e79 100644 --- a/Source/RWTHVRToolkit/Private/Interaction/Interactables/IntenSelect/IntenSelectable.cpp +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/IntenSelect/IntenSelectable.cpp @@ -62,7 +62,8 @@ void UIntenSelectable::HandleOnSelectStartEvents(const UIntenSelectComponent* In { for (const UHoverBehaviour* CurrentHoverBehaviour : OnHoverBehaviours) { - CurrentHoverBehaviour->OnHoverStartEvent.Broadcast(IntenSelect, HitResult); + CurrentHoverBehaviour->OnHoverEventEvent.Broadcast(IntenSelect, EInteractionEventType::InteractionStart, + HitResult); } } @@ -70,7 +71,9 @@ void UIntenSelectable::HandleOnSelectEndEvents(const UIntenSelectComponent* Inte { for (const UHoverBehaviour* CurrentHoverBehaviour : OnHoverBehaviours) { - CurrentHoverBehaviour->OnHoverEndEvent.Broadcast(IntenSelect); + FHitResult EmptyHit; + CurrentHoverBehaviour->OnHoverEventEvent.Broadcast(IntenSelect, EInteractionEventType::InteractionEnd, + EmptyHit); } } @@ -79,8 +82,8 @@ void UIntenSelectable::HandleOnClickStartEvents(UIntenSelectComponent* IntenSele for (const UActionBehaviour* CurrentActionBehaviour : OnActionBehaviours) { FInputActionValue EmptyInputActionValue{}; - const UInputAction* EmptyInputAction{}; - CurrentActionBehaviour->OnActionBeginEvent.Broadcast(IntenSelect, EmptyInputAction, EmptyInputActionValue); + CurrentActionBehaviour->OnActionEventEvent.Broadcast(IntenSelect, EInteractionEventType::InteractionStart, + EmptyInputActionValue); } } @@ -88,8 +91,8 @@ void UIntenSelectable::HandleOnClickEndEvents(UIntenSelectComponent* IntenSelect { for (const UActionBehaviour* CurrentActionBehaviour : OnActionBehaviours) { - const UInputAction* EmptyInputActionValue{}; - CurrentActionBehaviour->OnActionEndEvent.Broadcast(IntenSelect, EmptyInputActionValue, InputValue); + CurrentActionBehaviour->OnActionEventEvent.Broadcast(IntenSelect, EInteractionEventType::InteractionEnd, + InputValue); } } diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/InteractableComponent.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/InteractableComponent.cpp index 29ee75b08e1d37147671228e167c09e4cde462f8..4f1c256218355ce5c142d010bcc3a460491f08f8 100644 --- a/Source/RWTHVRToolkit/Private/Interaction/Interactables/InteractableComponent.cpp +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/InteractableComponent.cpp @@ -4,6 +4,9 @@ #include "Interaction/Interactables/InteractableComponent.h" #include "Interaction/Interactables/ActionBehaviour.h" #include "Interaction/Interactables/HoverBehaviour.h" +#include "Interaction/Interactors/UBaseInteractionComponent.h" +#include "Logging/StructuredLog.h" +#include "Utility/RWTHVRUtilities.h" void UInteractableComponent::RestrictInteractionToComponents(const TArray<USceneComponent*>& Components) { @@ -39,9 +42,8 @@ void UInteractableComponent::BeginPlay() InitDefaultBehaviourReferences(); } -// This functions dispatches the HoverStart Event to the attached Hover Behaviour Components -void UInteractableComponent::HandleOnHoverStartEvents(USceneComponent* TriggerComponent, - const EInteractorType Interactor) +void UInteractableComponent::HandleOnHoverEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor, + const EInteractionEventType EventType) { // We early return if there the InteractorFilter is set and the Interactor is allowed. if (!(InteractorFilter == EInteractorType::None || InteractorFilter & Interactor)) @@ -52,34 +54,47 @@ void UInteractableComponent::HandleOnHoverStartEvents(USceneComponent* TriggerCo return; // Broadcast event to all HoverBehaviours - for (const UHoverBehaviour* b : OnHoverBehaviours) + for (UHoverBehaviour* HoverBehaviour : OnHoverBehaviours) { - b->OnHoverStartEvent.Broadcast(TriggerComponent, HitResult); - } -} - -// This functions dispatches the HoverEnd Event to the attached Hover Behaviour Components -void UInteractableComponent::HandleOnHoverEndEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor) -{ - // We early return if there the InteractorFilter is set and the Interactor is allowed. - if (!(InteractorFilter == EInteractorType::None || InteractorFilter & Interactor)) - return; - - // We early return if the source Interactor is not part of the allowed components - if (!IsComponentAllowed(TriggerComponent)) - return; - - // Broadcast event to all HoverBehaviours - for (const UHoverBehaviour* b : OnHoverBehaviours) - { - b->OnHoverEndEvent.Broadcast(TriggerComponent); + // Check if we need to replicate the action to the server + if (HoverBehaviour->bExecuteOnServer) + { + // Request the server to execute our behaviour. + // As we can only execute RPCs from an owned object, and we don't necessarily own this actor, pipe it + // through the interactor. Because behaviours can also be triggered from non-interactors, only do this on a + // successful cast + + auto* InteractorComponent = Cast<UUBaseInteractionComponent>(TriggerComponent); + if (!InteractorComponent) + { + UE_LOGFMT(Toolkit, Warning, + "Interaction: TriggerComponent {TriggerComponent} is not a UUBaseInteractionComponent. Only " + "UUBaseInteractionComponent TriggerComponents can be replicated.", + TriggerComponent->GetName()); + return; + } + InteractorComponent->RequestHoverBehaviourReplicationStart(HoverBehaviour, EventType, HitResult); + + // Broadcast local callback + HoverBehaviour->OnHoverReplicationStartedOriginatorEvent.Broadcast(TriggerComponent, EventType, HitResult); + } + else if (HoverBehaviour->bExecuteOnAllClients) + { + UE_LOGFMT(Toolkit, Warning, + "Interaction: Behaviour {BehaviourName} has bExecuteOnAllClients=true, which requires " + "bExecuteOnServer also set to true, which is not the case.", + HoverBehaviour->GetName()); + } + else // skip replication altogether (default case) + { + HoverBehaviour->OnHoverEventEvent.Broadcast(TriggerComponent, EventType, HitResult); + } } } // This functions dispatches the ActionStart Event to the attached Action Behaviour Components -void UInteractableComponent::HandleOnActionStartEvents(USceneComponent* TriggerComponent, - const UInputAction* InputAction, const FInputActionValue& Value, - const EInteractorType Interactor) +void UInteractableComponent::HandleOnActionEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor, + const EInteractionEventType EventType, const FInputActionValue& Value) { // We early return if there the InteractorFilter is set and the Interactor is allowed. if (!(InteractorFilter == EInteractorType::None || InteractorFilter & Interactor)) @@ -90,28 +105,41 @@ void UInteractableComponent::HandleOnActionStartEvents(USceneComponent* TriggerC return; // Broadcast event to all ActionBehaviours - for (const UActionBehaviour* b : OnActionBehaviours) + for (UActionBehaviour* ActionBehaviour : OnActionBehaviours) { - b->OnActionBeginEvent.Broadcast(TriggerComponent, InputAction, Value); - } -} - -// This functions dispatches the ActionEnd Event to the attached Action Behaviour Components -void UInteractableComponent::HandleOnActionEndEvents(USceneComponent* TriggerComponent, const UInputAction* InputAction, - const FInputActionValue& Value, const EInteractorType Interactor) -{ - // We early return if there the InteractorFilter is set and the Interactor is allowed. - if (!(InteractorFilter == EInteractorType::None || InteractorFilter & Interactor)) - return; - - // We early return if the source Interactor is not part of the allowed components - if (!IsComponentAllowed(TriggerComponent)) - return; - - // Broadcast event to all ActionBehaviours - for (const UActionBehaviour* b : OnActionBehaviours) - { - b->OnActionEndEvent.Broadcast(TriggerComponent, InputAction, Value); + // Check if we need to replicate the action to the server + if (ActionBehaviour->bExecuteOnServer) + { + // Request the server to execute our behaviour. + // As we can only execute RPCs from an owned object, and we don't necessarily own this actor, pipe it + // through the interactor. Because behaviours can also be triggered from non-interactors, only do this on a + // successful cast + + auto* InteractorComponent = Cast<UUBaseInteractionComponent>(TriggerComponent); + if (!InteractorComponent) + { + UE_LOGFMT(Toolkit, Warning, + "Interaction: TriggerComponent {TriggerComponent} is not a UUBaseInteractionComponent. Only " + "UUBaseInteractionComponent TriggerComponents can be replicated.", + TriggerComponent->GetName()); + return; + } + InteractorComponent->RequestActionBehaviourReplicationStart(ActionBehaviour, EventType, Value); + + // Broadcast local callback + ActionBehaviour->OnActionReplicationStartedOriginatorEvent.Broadcast(TriggerComponent, EventType, Value); + } + else if (ActionBehaviour->bExecuteOnAllClients) + { + UE_LOGFMT(Toolkit, Warning, + "Interaction: Behaviour {BehaviourName} has bExecuteOnAllClients=true, which requires " + "bExecuteOnServer also set to true, which is not the case.", + ActionBehaviour->GetName()); + } + else // skip replication altogether (default case) + { + ActionBehaviour->OnActionEventEvent.Broadcast(TriggerComponent, EventType, Value); + } } } @@ -150,4 +178,4 @@ bool UInteractableComponent::IsComponentAllowed(USceneComponent* Component) cons } } return true; -} +} \ No newline at end of file diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactors/DirectInteractionComponent.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactors/DirectInteractionComponent.cpp index 7c81cb805f86e3d9a4de4bfa2c594ab02a683719..18e515817875cb60c35090b4dbfb4300051b8dac 100644 --- a/Source/RWTHVRToolkit/Private/Interaction/Interactors/DirectInteractionComponent.cpp +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactors/DirectInteractionComponent.cpp @@ -6,6 +6,7 @@ #include "EnhancedInputComponent.h" #include "Interaction/Interactables/InteractableComponent.h" #include "Interaction/Interactables/InteractionBitSet.h" +#include "Interaction/Interactables/InteractionEventType.h" #include "Kismet/GameplayStatics.h" #include "Logging/StructuredLog.h" @@ -30,7 +31,7 @@ void UDirectInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tick TArray<FHitResult> OutHits; const ETraceTypeQuery TraceType = UEngineTypes::ConvertToTraceType(ECollisionChannel::ECC_PhysicsBody); - auto DebugTrace = bShowDebugTrace ? EDrawDebugTrace::ForOneFrame : EDrawDebugTrace::None; + const auto DebugTrace = bShowDebugTrace ? EDrawDebugTrace::ForOneFrame : EDrawDebugTrace::None; UKismetSystemLibrary::SphereTraceMulti(GetWorld(), GetAttachParent()->GetComponentLocation(), GetAttachParent()->GetComponentLocation(), InteractionSphereRadius, @@ -59,7 +60,8 @@ void UDirectInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tick if (!PreviousInteractableComponentsInRange.Contains(CurrentInteractableComp)) { PreviousInteractableComponentsInRange.AddUnique(CurrentInteractableComp); - CurrentInteractableComp->HandleOnHoverStartEvents(this, EInteractorType::Direct); + CurrentInteractableComp->HandleOnHoverEvents(this, EInteractorType::Direct, + EInteractionEventType::InteractionStart); } } @@ -71,7 +73,8 @@ void UDirectInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tick if (!CurrentInteractableCompsInRange.Contains(PrevInteractableComp)) { ComponentsToRemove.AddUnique(PrevInteractableComp); - PrevInteractableComp->HandleOnHoverEndEvents(this, EInteractorType::Direct); + PrevInteractableComp->HandleOnHoverEvents(this, EInteractorType::Direct, + EInteractionEventType::InteractionEnd); } } @@ -97,12 +100,12 @@ void UDirectInteractionComponent::SetupPlayerInput(UInputComponent* PlayerInputC return; EI->BindAction(InteractionInputAction, ETriggerEvent::Started, this, - &UDirectInteractionComponent::OnBeginInteraction); + &UDirectInteractionComponent::OnBeginInteractionInputAction); EI->BindAction(InteractionInputAction, ETriggerEvent::Completed, this, - &UDirectInteractionComponent::OnEndInteraction); + &UDirectInteractionComponent::OnEndInteractionInputAction); } -void UDirectInteractionComponent::OnBeginInteraction(const FInputActionValue& Value) +void UDirectInteractionComponent::OnBeginInteractionInputAction(const FInputActionValue& Value) { const FVector InteractionLocation = GetAttachParent()->GetComponentLocation(); @@ -115,7 +118,7 @@ void UDirectInteractionComponent::OnBeginInteraction(const FInputActionValue& Va CurrentInteractableComponentsInRange, [&](auto Element) { return FVector(Element->GetOwner()->GetActorLocation() - InteractionLocation).Size(); }); - MinElement->HandleOnActionStartEvents(this, InteractionInputAction, Value, EInteractorType::Direct); + MinElement->HandleOnActionEvents(this, EInteractorType::Direct, EInteractionEventType::InteractionStart, Value); CurrentlyInteractedComponents = {MinElement}; } else @@ -125,19 +128,21 @@ void UDirectInteractionComponent::OnBeginInteraction(const FInputActionValue& Va CurrentInteractableComponentsInRange.Num()); for (UInteractableComponent* InteractableComp : CurrentInteractableComponentsInRange) { - InteractableComp->HandleOnActionStartEvents(this, InteractionInputAction, Value, EInteractorType::Direct); + InteractableComp->HandleOnActionEvents(this, EInteractorType::Direct, + EInteractionEventType::InteractionStart, Value); CurrentlyInteractedComponents.Add(InteractableComp); } } } -void UDirectInteractionComponent::OnEndInteraction(const FInputActionValue& Value) +void UDirectInteractionComponent::OnEndInteractionInputAction(const FInputActionValue& Value) { for (auto& Component : CurrentlyInteractedComponents) { if (Component.IsValid()) { - Component->HandleOnActionEndEvents(this, InteractionInputAction, Value, EInteractorType::Direct); + Component->HandleOnActionEvents(this, EInteractorType::Direct, EInteractionEventType::InteractionEnd, + Value); } } CurrentlyInteractedComponents.Empty(); diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactors/RWTHVRWidgetInteractionComponent.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactors/RWTHVRWidgetInteractionComponent.cpp index 3a87de65d2cb52a5bce0dd0aa71af9dd3b2f69bb..23e0727a0f17734282db4c1f0051965789b938ca 100644 --- a/Source/RWTHVRToolkit/Private/Interaction/Interactors/RWTHVRWidgetInteractionComponent.cpp +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactors/RWTHVRWidgetInteractionComponent.cpp @@ -51,10 +51,20 @@ void URWTHVRWidgetInteractionComponent::SetupPlayerInput(UInputComponent* Player return; } - EI->BindAction(WidgetClickInputAction, ETriggerEvent::Started, this, - &URWTHVRWidgetInteractionComponent::OnBeginClick); - EI->BindAction(WidgetClickInputAction, ETriggerEvent::Completed, this, - &URWTHVRWidgetInteractionComponent::OnEndClick); + if (WidgetLeftClickInputAction) + { + EI->BindAction(WidgetLeftClickInputAction, ETriggerEvent::Started, this, + &URWTHVRWidgetInteractionComponent::OnBeginLeftClick); + EI->BindAction(WidgetLeftClickInputAction, ETriggerEvent::Completed, this, + &URWTHVRWidgetInteractionComponent::OnEndLeftClick); + } + if (WidgetRightClickInputAction) + { + EI->BindAction(WidgetRightClickInputAction, ETriggerEvent::Started, this, + &URWTHVRWidgetInteractionComponent::OnBeginRightClick); + EI->BindAction(WidgetRightClickInputAction, ETriggerEvent::Completed, this, + &URWTHVRWidgetInteractionComponent::OnEndRightClick); + } } // Called every frame @@ -99,17 +109,29 @@ void URWTHVRWidgetInteractionComponent::SetInteractionRayVisibility(EInteraction } // Forward the click to the WidgetInteraction -void URWTHVRWidgetInteractionComponent::OnBeginClick(const FInputActionValue& Value) +void URWTHVRWidgetInteractionComponent::OnBeginLeftClick(const FInputActionValue& Value) { PressPointerKey(EKeys::LeftMouseButton); } // Forward the end click to the WidgetInteraction -void URWTHVRWidgetInteractionComponent::OnEndClick(const FInputActionValue& Value) +void URWTHVRWidgetInteractionComponent::OnEndLeftClick(const FInputActionValue& Value) { ReleasePointerKey(EKeys::LeftMouseButton); } +// Forward the click to the WidgetInteraction +void URWTHVRWidgetInteractionComponent::OnBeginRightClick(const FInputActionValue& Value) +{ + PressPointerKey(EKeys::RightMouseButton); +} + +// Forward the end click to the WidgetInteraction +void URWTHVRWidgetInteractionComponent::OnEndRightClick(const FInputActionValue& Value) +{ + ReleasePointerKey(EKeys::RightMouseButton); +} + void URWTHVRWidgetInteractionComponent::CreateInteractionRay() { // Only create a new static mesh component if we haven't gotten one already diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactors/RaycastInteractionComponent.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactors/RaycastInteractionComponent.cpp index 94afba4b4426fe1b861d787826f0e1cc11d6583b..42e1aeea61a94302b199769e8f79d50314c5cc89 100644 --- a/Source/RWTHVRToolkit/Private/Interaction/Interactors/RaycastInteractionComponent.cpp +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactors/RaycastInteractionComponent.cpp @@ -5,6 +5,7 @@ #include "EnhancedInputComponent.h" #include "Interaction/Interactables/InteractableComponent.h" +#include "Interaction/Interactables/InteractionEventType.h" #include "Kismet/KismetSystemLibrary.h" // Sets default values for this component's properties @@ -25,14 +26,13 @@ void URaycastInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tic UInteractableComponent* NewInteractableComponent = nullptr; - - TArray<AActor*> ActorsToIgnore; FHitResult Hit; const ETraceTypeQuery TraceType = UEngineTypes::ConvertToTraceType(ECollisionChannel::ECC_PhysicsBody); - FVector TraceStart = GetAttachParent()->GetComponentLocation(); - FVector TraceEnd = GetAttachParent()->GetComponentLocation() + TraceLength * GetAttachParent()->GetForwardVector(); + const FVector TraceStart = GetAttachParent()->GetComponentLocation(); + const FVector TraceEnd = + GetAttachParent()->GetComponentLocation() + TraceLength * GetAttachParent()->GetForwardVector(); - auto DebugTrace = bShowDebugTrace ? EDrawDebugTrace::ForOneFrame : EDrawDebugTrace::None; + const auto DebugTrace = bShowDebugTrace ? EDrawDebugTrace::ForOneFrame : EDrawDebugTrace::None; UKismetSystemLibrary::LineTraceSingle(GetWorld(), TraceStart, TraceEnd, TraceType, true, ActorsToIgnore, DebugTrace, Hit, true, FColor::Green); @@ -53,24 +53,28 @@ void URaycastInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tic if (CurrentInteractable != PreviousInteractable) { if (CurrentInteractable && CurrentInteractable->HasInteractionTypeFlag(EInteractorType::Raycast)) - CurrentInteractable->HandleOnHoverStartEvents(this, EInteractorType::Raycast); + CurrentInteractable->HandleOnHoverEvents(this, EInteractorType::Raycast, + EInteractionEventType::InteractionStart); if (PreviousInteractable && PreviousInteractable->HasInteractionTypeFlag(EInteractorType::Raycast)) - PreviousInteractable->HandleOnHoverEndEvents(this, EInteractorType::Raycast); + PreviousInteractable->HandleOnHoverEvents(this, EInteractorType::Raycast, + EInteractionEventType::InteractionEnd); } PreviousInteractable = CurrentInteractable; } -void URaycastInteractionComponent::OnBeginInteraction(const FInputActionValue& Value) +void URaycastInteractionComponent::OnBeginInteractionInputAction(const FInputActionValue& Value) { if (CurrentInteractable && CurrentInteractable->HasInteractionTypeFlag(EInteractorType::Raycast)) - CurrentInteractable->HandleOnActionStartEvents(this, InteractionInputAction, Value, EInteractorType::Raycast); + CurrentInteractable->HandleOnActionEvents(this, EInteractorType::Raycast, + EInteractionEventType::InteractionStart, Value); } -void URaycastInteractionComponent::OnEndInteraction(const FInputActionValue& Value) +void URaycastInteractionComponent::OnEndInteractionInputAction(const FInputActionValue& Value) { if (CurrentInteractable && CurrentInteractable->HasInteractionTypeFlag(EInteractorType::Raycast)) - CurrentInteractable->HandleOnActionEndEvents(this, InteractionInputAction, Value, EInteractorType::Raycast); + CurrentInteractable->HandleOnActionEvents(this, EInteractorType::Raycast, EInteractionEventType::InteractionEnd, + Value); } void URaycastInteractionComponent::SetupPlayerInput(UInputComponent* PlayerInputComponent) @@ -86,7 +90,7 @@ void URaycastInteractionComponent::SetupPlayerInput(UInputComponent* PlayerInput return; EI->BindAction(InteractionInputAction, ETriggerEvent::Started, this, - &URaycastInteractionComponent::OnBeginInteraction); + &URaycastInteractionComponent::OnBeginInteractionInputAction); EI->BindAction(InteractionInputAction, ETriggerEvent::Completed, this, - &URaycastInteractionComponent::OnEndInteraction); + &URaycastInteractionComponent::OnEndInteractionInputAction); } diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactors/UBaseInteractionComponent.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactors/UBaseInteractionComponent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..67d718c90cdff0dce7dab7cf7bff226f778877c9 --- /dev/null +++ b/Source/RWTHVRToolkit/Private/Interaction/Interactors/UBaseInteractionComponent.cpp @@ -0,0 +1,152 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Interaction/Interactors/UBaseInteractionComponent.h" + +#include "Interaction/Interactables/ActionBehaviour.h" +#include "Interaction/Interactables/HoverBehaviour.h" +#include "Logging/StructuredLog.h" +#include "Utility/RWTHVRUtilities.h" + +// Sets default values for this component's properties +UUBaseInteractionComponent::UUBaseInteractionComponent() +{ + // 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; + SetIsReplicatedByDefault(true); + // ... +} + +void UUBaseInteractionComponent::SetupPlayerInput(UInputComponent* PlayerInputComponent) +{ + IInputExtensionInterface::SetupPlayerInput(PlayerInputComponent); +} + +void UUBaseInteractionComponent::OnBeginInteractionInputAction(const FInputActionValue& Value) {} + +void UUBaseInteractionComponent::OnEndInteractionInputAction(const FInputActionValue& Value) {} + +void UUBaseInteractionComponent::RequestHoverBehaviourReplicationStart(UHoverBehaviour* Behaviour, + const EInteractionEventType EventType, + const FHitResult& Hit) +{ + if (GetOwner()->HasLocalNetOwner()) + { + if (GetOwner()->HasAuthority()) + { + HoverBehaviourReplicationStart(Behaviour, EventType, Hit); + } + else + { + // RPC + ServerRequestHoverBehaviourReplicationStartRpc(Behaviour, EventType, Hit); + } + } + else + { + UE_LOGFMT(Toolkit, Error, + "Interaction: Trying to replicate HoverBehaviour {HoverBehaviour} in InteractionComponent " + "{InteractionComponent} which has no local net owner!", + Behaviour->GetName(), this->GetName()); + } +} + +void UUBaseInteractionComponent::HoverBehaviourReplicationStart(UHoverBehaviour* Behaviour, + const EInteractionEventType EventType, + const FHitResult& Hit) +{ + if (!Behaviour->bExecuteOnServer) // this should never happen + { + UE_LOGFMT(Toolkit, Error, + "Interaction: Trying to execute HoverBehaviour {HoverBehaviour} in InteractionComponent " + "{InteractionComponent} on the server, but bExecuteOnServer is false!", + Behaviour->GetName(), this->GetName()); + return; + } + + // If desired, multicast to all clients (including originator) + if (Behaviour->bExecuteOnAllClients) + { + MulticastHoverBehaviourReplicationStartRpc(Behaviour, EventType, Hit); + } + else + { + // Execute the behaviour only on the server + Behaviour->OnHoverEventEvent.Broadcast(this, EventType, Hit); + } +} + +void UUBaseInteractionComponent::ServerRequestHoverBehaviourReplicationStartRpc_Implementation( + UHoverBehaviour* Behaviour, const EInteractionEventType EventType, const FHitResult& Hit) +{ + HoverBehaviourReplicationStart(Behaviour, EventType, Hit); +} + +void UUBaseInteractionComponent::MulticastHoverBehaviourReplicationStartRpc_Implementation( + UHoverBehaviour* Behaviour, const EInteractionEventType EventType, const FHitResult& Hit) +{ + Behaviour->OnHoverEventEvent.Broadcast(this, EventType, Hit); +} + +void UUBaseInteractionComponent::RequestActionBehaviourReplicationStart(UActionBehaviour* Behaviour, + const EInteractionEventType EventType, + const FInputActionValue& Value) +{ + if (GetOwner()->HasLocalNetOwner()) + { + if (GetOwner()->HasAuthority()) + { + ActionBehaviourReplicationStart(Behaviour, EventType, Value); + } + else + { + // RPC + ServerRequestActionBehaviourReplicationStartRpc(Behaviour, EventType, Value); + } + } + else + { + UE_LOGFMT(Toolkit, Error, + "Interaction: Trying to replicate HoverBehaviour {HoverBehaviour} in InteractionComponent " + "{InteractionComponent} which has no local net owner!", + Behaviour->GetName(), this->GetName()); + } +} + +void UUBaseInteractionComponent::ActionBehaviourReplicationStart(UActionBehaviour* Behaviour, + const EInteractionEventType EventType, + const FInputActionValue& Value) +{ + if (!Behaviour->bExecuteOnServer) // this should never happen + { + UE_LOGFMT(Toolkit, Error, + "Interaction: Trying to execute ActionBehaviour {ActionBehaviour} in InteractionComponent " + "{InteractionComponent} on the server, but bExecuteOnServer is false!", + Behaviour->GetName(), this->GetName()); + return; + } + + // If desired, multicast to all clients (including originator) + if (Behaviour->bExecuteOnAllClients) + { + MulticastActionBehaviourReplicationStartRpc(Behaviour, EventType, Value); + } + else + { + // Execute the behaviour only on the server + Behaviour->OnActionEventEvent.Broadcast(this, EventType, Value); + } +} + +void UUBaseInteractionComponent::ServerRequestActionBehaviourReplicationStartRpc_Implementation( + UActionBehaviour* Behaviour, const EInteractionEventType EventType, const FInputActionValue& Value) +{ + ActionBehaviourReplicationStart(Behaviour, EventType, Value); +} + +void UUBaseInteractionComponent::MulticastActionBehaviourReplicationStartRpc_Implementation( + UActionBehaviour* Behaviour, const EInteractionEventType EventType, const FInputActionValue& Value) +{ + Behaviour->OnActionEventEvent.Broadcast(this, EventType, Value); +} diff --git a/Source/RWTHVRToolkit/Private/Pawn/ClusterRepresentationActor.cpp b/Source/RWTHVRToolkit/Private/Pawn/ClusterRepresentationActor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a280d911113902d4918555b0a6b425d31e97ff8e --- /dev/null +++ b/Source/RWTHVRToolkit/Private/Pawn/ClusterRepresentationActor.cpp @@ -0,0 +1,170 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Pawn/ClusterRepresentationActor.h" +#include "Core/RWTHVRPlayerState.h" +#include "Kismet/GameplayStatics.h" +#include "Logging/StructuredLog.h" +#include "Utility/RWTHVRUtilities.h" + +#if PLATFORM_SUPPORTS_CLUSTER +#include "DisplayClusterRootActor.h" +#include "IDisplayCluster.h" +#include "Config/IDisplayClusterConfigManager.h" +#include "Game/IDisplayClusterGameManager.h" +#endif + +// Sets default values +AClusterRepresentationActor::AClusterRepresentationActor() +{ + // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. + PrimaryActorTick.bCanEverTick = false; + bReplicates = true; + SetActorEnableCollision(false); + + SetRootComponent(CreateDefaultSubobject<USceneComponent>(TEXT("Origin"))); +} + +void AClusterRepresentationActor::BeginPlay() +{ + Super::BeginPlay(); + // will fail if we're in replicated mode and PlayerState has not yet replicated fully + // Therefore we also execute this + AttachDCRAIfRequired(); +} + +void AClusterRepresentationActor::AttachDCRAIfRequired(const ARWTHVRPlayerState* OptionalPlayerState) +{ + // We need to identify the correct ClusterRepresentationActor to do the attachment. + // 1. Either we are the local net owner -> Primary Node Pawn + // This is hard to do as things might not be synchronized yet. + // In this case I think this is save to do because either: + // - We are in standalone mode, then attach it for all nodes + // - If we are a client, this actor has been spawned on the server only. Therefore I assume that if we + // have replicated this actor to our client, we're good to go. + +#if PLATFORM_SUPPORTS_CLUSTER + + if (!URWTHVRUtilities::IsRoomMountedMode()) + return; + + if (bIsAttached) + { + UE_LOGFMT(Toolkit, Display, "{Name} AttachDCRAIfRequired: Already attached, skipping repeated attachment.", + GetName()); + return; + } + + UE_LOGFMT(Toolkit, Display, "{Name} AttachDCRAIfRequired: Starting DCRA Attachment process.", GetName()); + + // This should give us the first local player controller + const auto* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0); + + // Only run this for the local controller - redundant, but double check + if (!PlayerController || !PlayerController->IsLocalController()) + { + UE_LOGFMT(Toolkit, Warning, + "{Name} AttachDCRAIfRequired: PlayerController not valid or not locally controlled.", GetName()); + return; + } + const auto* PlayerState = + OptionalPlayerState != nullptr ? OptionalPlayerState : PlayerController->GetPlayerState<ARWTHVRPlayerState>(); + if (!PlayerState) + { + UE_LOGFMT(Toolkit, Error, + "{Name} AttachDCRAIfRequired: PlayerState is not valid or not of type " + "ARWTHVRPlayerState.", + GetName()); + return; + } + + const auto CCA = PlayerState->GetCorrespondingClusterActor(); + + if (CCA == nullptr) // this can happen often if property isn't replicated yet, this is okay. + return; + + UE_LOGFMT(Toolkit, Display, + "{Name} AttachDCRAIfRequired: Player State is {PlayerState}, PlayerState->CCA is {CCA}.", GetName(), + PlayerState->GetName(), CCA->GetName()); + + // The local player this is executed on corresponds to this actor + if (CCA == this) + { + UE_LOGFMT(Toolkit, Display, "{Name} AttachDCRAIfRequired: Attaching DCRA to {Name}.", GetName(), + CCA->GetName()); + + bIsAttached = AttachDCRA(); + } +#endif +} + +#if PLATFORM_SUPPORTS_CLUSTER +bool AClusterRepresentationActor::AttachDCRA() +{ + if (URWTHVRUtilities::IsRoomMountedMode()) + { + UE_LOGFMT(Toolkit, Display, "{Name}: Trying to attach DCRA", GetName()); + auto DCRA = IDisplayCluster::Get().GetGameMgr()->GetRootActor(); + + if (!IsValid(DCRA)) + { + UE_LOGFMT(Toolkit, Display, "No Valid DCRA in BeginPlay. Spawning manually."); + + DCRA = SpawnDCRA(); + if (!IsValid(DCRA)) + { + UE_LOGFMT(Toolkit, Error, "Failed to spawn correct DCRA, cannot attach it to ClusterRepresentation."); + return false; + } + } + else // if we just spawned the DCRA, it is not yet the primary one and this check makes no sense + { + if (!DCRA->IsPrimaryRootActor()) + { + UE_LOGFMT(Toolkit, Error, "Found DCRA {Name} is not the primary DCRA of Cluster with Id!", + DCRA->GetName()); + return false; + } + } + + bool bAttached = DCRA->AttachToActor(this, FAttachmentTransformRules::SnapToTargetNotIncludingScale); + UE_LOGFMT(Toolkit, Display, "Attaching DCRA {DCRA} to {this} returned {Res}", DCRA->GetName(), GetName(), + bAttached); + + DCRA->SetActorEnableCollision(false); + } + return true; +} + +ADisplayClusterRootActor* AClusterRepresentationActor::SpawnDCRA() +{ + UDisplayClusterConfigurationData* ConfigData = IDisplayCluster::Get().GetConfigMgr()->GetConfig(); + + + // Function similar to the one from DisplayClusterGameManager. + ADisplayClusterRootActor* RootActor = nullptr; + // We need to use generated class as it's the only one available in packaged buidls + const FString AssetPath = + (ConfigData->Info.AssetPath.EndsWith(TEXT("_C")) ? ConfigData->Info.AssetPath + : ConfigData->Info.AssetPath + TEXT("_C")); + + if (UClass* ActorClass = Cast<UClass>(StaticLoadObject(UClass::StaticClass(), nullptr, *AssetPath))) + { + // Spawn the actor + if (AActor* NewActor = GetWorld()->SpawnActor<AActor>(ActorClass)) + { + RootActor = Cast<ADisplayClusterRootActor>(NewActor); + + if (RootActor) + { + UE_LOG(Toolkit, Log, TEXT("Instantiated DCRA from asset '%s'"), *ConfigData->Info.AssetPath); + + // Override actor settings in case the config file contains some updates + RootActor->OverrideFromConfig(ConfigData); + } + } + } + return RootActor; +} + +#endif diff --git a/Source/RWTHVRToolkit/Private/Pawn/Navigation/CollisionHandlingMovement.cpp b/Source/RWTHVRToolkit/Private/Pawn/Navigation/CollisionHandlingMovement.cpp index d75c4d401caf16d8485458db2ed4f4e78c4689d3..62bfa4602491506185646dfd769a3876ca4b8ccb 100644 --- a/Source/RWTHVRToolkit/Private/Pawn/Navigation/CollisionHandlingMovement.cpp +++ b/Source/RWTHVRToolkit/Private/Pawn/Navigation/CollisionHandlingMovement.cpp @@ -1,6 +1,8 @@ #include "Pawn/Navigation/CollisionHandlingMovement.h" #include "Kismet/KismetSystemLibrary.h" +#include "Logging/StructuredLog.h" +#include "Utility/RWTHVRUtilities.h" UCollisionHandlingMovement::UCollisionHandlingMovement(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) @@ -33,61 +35,76 @@ void UCollisionHandlingMovement::BeginPlay() void UCollisionHandlingMovement::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { - SetCapsuleColliderToUserSize(); - FVector InputVector = GetPendingInputVector(); - - if (NavigationMode == EVRNavigationModes::NAV_WALK) + if (ShouldSkipUpdate(DeltaTime)) { - // you are only allowed to move horizontally in NAV_WALK - // everything else will be handled by stepping-up/gravity - // so rotate the input vector onto horizontal plane - const FRotator InputRot = FRotator(InputVector.Rotation()); - const FRotator InputYaw = FRotator(0, InputRot.Yaw, 0); - InputVector = InputRot.UnrotateVector(InputVector); - InputVector = InputYaw.RotateVector(InputVector); - ConsumeInputVector(); - AddInputVector(InputVector); + return; } - - if (NavigationMode == EVRNavigationModes::NAV_FLY || NavigationMode == EVRNavigationModes::NAV_WALK) + const AController* Controller = PawnOwner->GetController(); + if (Controller && Controller->IsLocalController()) { - // if me managed to get into a collision revert the movement since last Tick - CheckAndRevertCollisionSinceLastTick(); - // check whether we are still in collision e.g. if an object has moved and got us into collision - MoveOutOfNewDynamicCollisions(); + SetCapsuleColliderToUserSize(); - if (InputVector.Size() > 0.001) + FVector InputVector = GetPendingInputVector(); + + if (NavigationMode == EVRNavigationModes::NAV_WALK) { - const FVector SafeSteeringInput = GetCollisionSafeVirtualSteeringVec(InputVector, DeltaTime); - if (SafeSteeringInput != InputVector) + // you are only allowed to move horizontally in NAV_WALK + // everything else will be handled by stepping-up/gravity + // so rotate the input vector onto horizontal plane + const FRotator InputRot = FRotator(InputVector.Rotation()); + const FRotator InputYaw = FRotator(0, InputRot.Yaw, 0); + InputVector = InputRot.UnrotateVector(InputVector); + InputVector = InputYaw.RotateVector(InputVector); + ConsumeInputVector(); + AddInputVector(InputVector); + } + + if (NavigationMode == EVRNavigationModes::NAV_FLY || NavigationMode == EVRNavigationModes::NAV_WALK) + { + // if me managed to get into a collision revert the movement since last Tick + CheckAndRevertCollisionSinceLastTick(); + // check whether we are still in collision e.g. if an object has moved and got us into collision + MoveOutOfNewDynamicCollisions(); + + if (InputVector.Size() > 0.001) + { + const FVector SafeSteeringInput = GetCollisionSafeVirtualSteeringVec(InputVector, DeltaTime); + if (SafeSteeringInput != InputVector) + { + // if we would move into something if we apply this input (estimating distance by max speed) + // we only apply its perpendicular part (unless it is facing away from the collision) + ConsumeInputVector(); + AddInputVector(SafeSteeringInput); + } + } + + // in case we are in a collision and collision checks are temporarily deactivated, we only allow physical + // movement without any checks, otherwise check collision during physical movement + if (bCollisionChecksTemporarilyDeactivated) { - // if we would move into something if we apply this input (estimating distance by max speed) - // we only apply its perpendicular part (unless it is facing away from the collision) ConsumeInputVector(); - AddInputVector(SafeSteeringInput); + } + else + { + // so we add stepping-up (for both walk and fly) + // and gravity for walking only + MoveByGravityOrStepUp(DeltaTime); + + // if we physically (in the tracking space) walked into something, move the world away (by moving the + // pawn) + CheckForPhysWalkingCollision(); } } - // in case we are in a collision and collision checks are temporarily deactivated. - if (!bCollisionChecksTemporarilyDeactivated) + if (NavigationMode == EVRNavigationModes::NAV_NONE) { - // so we add stepping-up (for both walk and fly) - // and gravity for walking only - MoveByGravityOrStepUp(DeltaTime); - - // if we physically (in the tracking space) walked into something, move the world away (by moving the pawn) - CheckForPhysWalkingCollision(); + // just remove whatever input is there + ConsumeInputVector(); } } - if (NavigationMode == EVRNavigationModes::NAV_NONE) - { - // just remove whatever input is there - ConsumeInputVector(); - } - Super::TickComponent(DeltaTime, TickType, ThisTickFunction); } @@ -100,6 +117,34 @@ void UCollisionHandlingMovement::SetHeadComponent(USceneComponent* NewHeadCompon CapsuleColliderComponent->SetWorldLocation(FVector(0.0f, 0.0f, -HalfHeight)); } +bool UCollisionHandlingMovement::AddActorToIgnore(AActor* ActorToIgnore) +{ + if (ActorToIgnore && IsValid(ActorToIgnore)) + { + ActorsToIgnore.AddUnique(ActorToIgnore); + return true; + } + else + { + UE_LOGFMT(Toolkit, Warning, "UCollisionHandlingMovement::AddActorToIgnore: Cannot add invalid actor"); + return false; + } +} + +bool UCollisionHandlingMovement::RemoveActorFromIgnore(AActor* ActorToIgnore) +{ + if (ActorToIgnore && IsValid(ActorToIgnore)) + { + const int32 NumRemoved = ActorsToIgnore.Remove(ActorToIgnore); + return NumRemoved > 0; + } + else + { + UE_LOGFMT(Toolkit, Warning, "UCollisionHandlingMovement::RemoveActorFromIgnore: Cannot remove invalid actor"); + return false; + } +} + void UCollisionHandlingMovement::SetCapsuleColliderToUserSize() const { // the collider should be placed diff --git a/Source/RWTHVRToolkit/Private/Pawn/Navigation/TurnComponent.cpp b/Source/RWTHVRToolkit/Private/Pawn/Navigation/TurnComponent.cpp index 9388a11d61314e83624b5a173e831192f07c6b6e..7e3aee8e16027a8d7e55379c38ca4fe0bc2ac537 100644 --- a/Source/RWTHVRToolkit/Private/Pawn/Navigation/TurnComponent.cpp +++ b/Source/RWTHVRToolkit/Private/Pawn/Navigation/TurnComponent.cpp @@ -37,22 +37,36 @@ void UTurnComponent::SetupPlayerInput(UInputComponent* PlayerInputComponent) // turning if (bAllowTurning) { - // no snap turning for desktop mode - if (bSnapTurn && !URWTHVRUtilities::IsDesktopMode()) + if (bSnapTurn) { - EI->BindAction(Turn, ETriggerEvent::Started, this, &UTurnComponent::OnBeginSnapTurn); + // no snap turning for desktop mode + if (!URWTHVRUtilities::IsDesktopMode()) + { + EI->BindAction(XRTurn, ETriggerEvent::Started, this, &UTurnComponent::OnBeginSnapTurn); + } + else + { + EI->BindAction(DesktopTurn, ETriggerEvent::Triggered, this, &UTurnComponent::OnBeginTurn); + } } else { - EI->BindAction(Turn, ETriggerEvent::Triggered, this, &UTurnComponent::OnBeginTurn); + if (!URWTHVRUtilities::IsDesktopMode()) + { + EI->BindAction(XRTurn, ETriggerEvent::Triggered, this, &UTurnComponent::OnBeginTurn); + } + else + { + EI->BindAction(DesktopTurn, ETriggerEvent::Triggered, this, &UTurnComponent::OnBeginTurn); + } } } // bind additional functions for desktop rotations if (URWTHVRUtilities::IsDesktopMode()) { - EI->BindAction(DesktopRotation, ETriggerEvent::Started, this, &UTurnComponent::StartDesktopRotation); - EI->BindAction(DesktopRotation, ETriggerEvent::Completed, this, &UTurnComponent::EndDesktopRotation); + EI->BindAction(DesktopTurnCondition, ETriggerEvent::Started, this, &UTurnComponent::StartDesktopRotation); + EI->BindAction(DesktopTurnCondition, ETriggerEvent::Completed, this, &UTurnComponent::EndDesktopRotation); } } diff --git a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp index d50578531749b295d79961ac45eab7ebfd097c7e..812e83ea74b96709e72b7768dbbd5d90bc041f52 100644 --- a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp +++ b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp @@ -8,8 +8,8 @@ #include "ILiveLinkClient.h" #include "InputMappingContext.h" #include "Core/RWTHVRPlayerState.h" -#include "Kismet/GameplayStatics.h" #include "Logging/StructuredLog.h" +#include "Pawn/ClusterRepresentationActor.h" #include "Pawn/InputExtensionInterface.h" #include "Pawn/Navigation/CollisionHandlingMovement.h" #include "Pawn/ReplicatedCameraComponent.h" @@ -61,7 +61,7 @@ void ARWTHVRPawn::Tick(float DeltaSeconds) * as connections now send their playertype over. */ // This pawn's controller has changed! This is called on both server and owning client. If we are the owning client -// and the master, request that the DCRA is attached to us. +// and the master, request that the Cluster is attached to us. void ARWTHVRPawn::NotifyControllerChanged() { Super::NotifyControllerChanged(); @@ -71,17 +71,17 @@ void ARWTHVRPawn::NotifyControllerChanged() if (HasAuthority()) { UE_LOG(Toolkit, Display, - TEXT("ARWTHVRPawn: Player Controller has changed, trying to change DCRA attachment if possible...")); + TEXT("ARWTHVRPawn: Player Controller has changed, trying to change Cluster attachment if possible...")); if (const ARWTHVRPlayerState* State = GetPlayerState<ARWTHVRPlayerState>()) { const EPlayerType Type = State->GetPlayerType(); // Only cluster types are valid here as they are set on connection. // For all other player types this is a race condition - if (Type == EPlayerType::nDisplayPrimary || GetNetMode() == NM_Standalone) + if (Type == EPlayerType::nDisplayPrimary || Type == EPlayerType::nDisplaySecondary) { - UE_LOGFMT(Toolkit, Display, "ARWTHVRPawn: Attaching DCRA to Pawn {Pawn}.", GetName()); - AttachDCRAtoPawn(); + UE_LOGFMT(Toolkit, Display, "ARWTHVRPawn: Attaching Cluster to Pawn {Pawn}.", GetName()); + AttachClustertoPawn(); } } } @@ -249,7 +249,7 @@ void ARWTHVRPawn::MulticastAddDCSyncComponent_Implementation() // for some reason acts different on the nodes, therefore leading to a potential desync when // e.g. colliding with an object while moving. - if (URWTHVRUtilities::IsRoomMountedMode()) + if (URWTHVRUtilities::IsRoomMountedMode() && !SyncComponent) { SyncComponent = Cast<USceneComponent>(AddComponentByClass( UDisplayClusterSceneComponentSyncParent::StaticClass(), false, FTransform::Identity, false)); @@ -259,35 +259,35 @@ void ARWTHVRPawn::MulticastAddDCSyncComponent_Implementation() #endif } -// Executed on the server only: Finds and attaches the CaveSetup Actor, which contains the DCRA to the Pawn. +// Executed on the server only: Attaches the ClusterRepresentation Actor, which contains the DCRA to the Pawn. // It is only executed on the server because attachments are synced to all clients, but not from client to server. -void ARWTHVRPawn::AttachDCRAtoPawn() +void ARWTHVRPawn::AttachClustertoPawn() { - if (!CaveSetupActorClass || !CaveSetupActorClass->IsValidLowLevelFast()) + if (const ARWTHVRPlayerState* State = GetPlayerState<ARWTHVRPlayerState>()) { - UE_LOGFMT(Toolkit, Warning, "No CaveSetup Actor class set in pawn!"); - return; - } - - // Find our CaveSetup actor - TArray<AActor*> FoundActors; - UGameplayStatics::GetAllActorsOfClass(GetWorld(), CaveSetupActorClass, FoundActors); - - if (!FoundActors.IsEmpty()) - { - const auto CaveSetupActor = FoundActors[0]; - FAttachmentTransformRules AttachmentRules = FAttachmentTransformRules::SnapToTargetNotIncludingScale; - CaveSetupActor->AttachToActor(this, AttachmentRules); - UE_LOGFMT(Toolkit, Display, "VirtualRealityPawn: Attaching CaveSetup to our pawn!"); + const auto ClusterActor = State->GetCorrespondingClusterActor(); + if (!ClusterActor) + { + UE_LOGFMT( + Toolkit, Error, + "ARWTHVRPawn::AttachClustertoPawn: GetCorrespondingClusterActor returned null! This won't work on " + "the Cave."); + return; + } + const FAttachmentTransformRules AttachmentRules = FAttachmentTransformRules::SnapToTargetNotIncludingScale; + bool bAttached = ClusterActor->AttachToComponent(GetRootComponent(), AttachmentRules); + // State->GetCorrespondingClusterActor()->OnAttached(); + UE_LOGFMT(Toolkit, Display, + "ARWTHVRPawn: Attaching corresponding cluster actor to our pawn returned: {Attached}", bAttached); } else { - UE_LOGFMT(Toolkit, Warning, - "No CaveSetup Actor found which can be attached to the Pawn! This won't work on the Cave."); + UE_LOGFMT(Toolkit, Error, + "ARWTHVRPawn::AttachClustertoPawn: No ARWTHVRPlayerState set! This won't work on the Cave."); } - // if (HasAuthority()) // Should always be the case here, but double check - // MulticastAddDCSyncComponent(); + if (HasAuthority()) // Should always be the case here, but double check + MulticastAddDCSyncComponent(); } void ARWTHVRPawn::SetupMotionControllerSources() diff --git a/Source/RWTHVRToolkit/Public/Core/RWTHVRGameModeBase.h b/Source/RWTHVRToolkit/Public/Core/RWTHVRGameModeBase.h index 1469ac36caa9b571b765622766a01ed103b26252..be8e65d9da82cf7acc215bc8154ec9772fc7537c 100644 --- a/Source/RWTHVRToolkit/Public/Core/RWTHVRGameModeBase.h +++ b/Source/RWTHVRToolkit/Public/Core/RWTHVRGameModeBase.h @@ -4,6 +4,7 @@ #include "CoreMinimal.h" #include "GameFramework/GameModeBase.h" +#include "Pawn/ClusterRepresentationActor.h" #include "RWTHVRGameModeBase.generated.h" /** @@ -15,6 +16,8 @@ UCLASS() class RWTHVRTOOLKIT_API ARWTHVRGameModeBase : public AGameModeBase { GENERATED_BODY() +public: + ARWTHVRGameModeBase(const FObjectInitializer& ObjectInitializer); protected: /** @@ -29,4 +32,8 @@ protected: * possess. If not, spawn a DefaultPawnClass Pawn and Possess it (Should be BP_VirtualRealityPawn to make sense). */ virtual void PostLogin(APlayerController* NewPlayer) override; + +private: + UPROPERTY() + TMap<int32, AClusterRepresentationActor*> ConnectedClusters; }; diff --git a/Source/RWTHVRToolkit/Public/Core/RWTHVRPlayerState.h b/Source/RWTHVRToolkit/Public/Core/RWTHVRPlayerState.h index 4c709e204428e4cbcd20d782823676c5b8e12c7c..040c251a6db94e6564b83d120bfd62d7bb6d57e0 100644 --- a/Source/RWTHVRToolkit/Public/Core/RWTHVRPlayerState.h +++ b/Source/RWTHVRToolkit/Public/Core/RWTHVRPlayerState.h @@ -5,8 +5,13 @@ #include "CoreMinimal.h" #include "PlayerType.h" #include "GameFramework/PlayerState.h" +#include "Logging/StructuredLog.h" +#include "Pawn/ClusterRepresentationActor.h" +#include "Utility/RWTHVRUtilities.h" #include "RWTHVRPlayerState.generated.h" + +class AClusterRepresentationActor; enum class EPlayerType : uint8; /** * Extension of the PlayerState that additionally holds information about what type the player is. @@ -22,6 +27,26 @@ private: UPROPERTY(Replicated, Category = PlayerState, BlueprintGetter = GetPlayerType, meta = (AllowPrivateAccess)) EPlayerType PlayerType = EPlayerType::Desktop; + /** Replicated cluster ID for this player. Is -1 in case player is not a cluster*/ + UPROPERTY(Replicated, Category = PlayerState, BlueprintGetter = GetCorrespondingClusterId, + meta = (AllowPrivateAccess)) + int32 CorrespondingClusterId = -1; + + /** Replicated cluster actor for this player. Is nullptr in case player is not a cluster. + * As this is not guaranteed to be valid on BeginPlay, we need to do a callback to the CorrespondingClusterActor + * here... + */ + UPROPERTY(ReplicatedUsing = OnRep_CorrespondingClusterActor) + TObjectPtr<AClusterRepresentationActor> CorrespondingClusterActor; + + UFUNCTION() + virtual void OnRep_CorrespondingClusterActor() + { + // Only execute this on the local owning player + if (HasLocalNetOwner()) + CorrespondingClusterActor->AttachDCRAIfRequired(this); + } + UFUNCTION(Reliable, Server) void ServerSetPlayerTypeRpc(EPlayerType NewPlayerType); @@ -31,6 +56,15 @@ public: UFUNCTION(BlueprintGetter) EPlayerType GetPlayerType() const { return PlayerType; } + UFUNCTION(BlueprintGetter) + int32 GetCorrespondingClusterId() const { return CorrespondingClusterId; } + + UFUNCTION(BlueprintGetter) + AClusterRepresentationActor* GetCorrespondingClusterActor() const { return CorrespondingClusterActor; } + + void SetCorrespondingClusterId(int32 NewCorrespondingClusterId); + void SetCorrespondingClusterActor(AClusterRepresentationActor* NewCorrespondingClusterActor); + UFUNCTION(BlueprintCallable) void RequestSetPlayerType(EPlayerType NewPlayerType); diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactables/ActionBehaviour.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/ActionBehaviour.h index 755d876dc3205b0538f6bc394352def55b2da670..bf3afea3a1246994a0bcbfb1d96317083e2770e3 100644 --- a/Source/RWTHVRToolkit/Public/Interaction/Interactables/ActionBehaviour.h +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/ActionBehaviour.h @@ -3,21 +3,18 @@ #pragma once #include "CoreMinimal.h" -#include "InputAction.h" +#include "BaseBehaviour.h" #include "Components/SceneComponent.h" #include "InputActionValue.h" -#include "InteractionBitSet.h" #include "ActionBehaviour.generated.h" +enum EInteractionEventType : uint8; -DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnActionBegin, USceneComponent*, TriggeredComponent, - const UInputAction*, InputAction, const FInputActionValue&, Value); - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnActionEnd, USceneComponent*, TriggeredComponent, const UInputAction*, - InputAction, const FInputActionValue&, Value); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnActionBegin, USceneComponent*, TriggerComponent, + const EInteractionEventType, EventType, const FInputActionValue&, Value); UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) -class RWTHVRTOOLKIT_API UActionBehaviour : public USceneComponent +class RWTHVRTOOLKIT_API UActionBehaviour : public UBaseBehaviour { GENERATED_BODY() @@ -26,22 +23,23 @@ public: UActionBehaviour(); UPROPERTY(BlueprintAssignable) - FOnActionBegin OnActionBeginEvent; - + FOnActionBegin OnActionEventEvent; + + /** + * Replication specific: + * This is only executed on the local client which processed the interaction and requested the replication process + * to be started. Can be used e.g. for local effects or things that should be done both on the server and local + * client. Broadcast by UInteractableComponent when the originating client sends a server rpc to start the + * interaction replication. + */ UPROPERTY(BlueprintAssignable) - FOnActionEnd OnActionEndEvent; + FOnActionBegin OnActionReplicationStartedOriginatorEvent; + protected: UFUNCTION() - virtual void OnActionStart(USceneComponent* TriggeredComponent, const UInputAction* InputAction, + virtual void OnActionEvent(USceneComponent* TriggerComponent, const EInteractionEventType EventType, const FInputActionValue& Value); - UFUNCTION() - virtual void OnActionEnd(USceneComponent* TriggeredComponent, const UInputAction* InputAction, - const FInputActionValue& Value); - virtual void BeginPlay() override; - - virtual void TickComponent(float DeltaTime, ELevelTick TickType, - FActorComponentTickFunction* ThisTickFunction) override; }; diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactables/BaseBehaviour.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/BaseBehaviour.h new file mode 100644 index 0000000000000000000000000000000000000000..d1f379bb9431749786d2ac871d119e82f5bea46e --- /dev/null +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/BaseBehaviour.h @@ -0,0 +1,34 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/SceneComponent.h" +#include "BaseBehaviour.generated.h" + + +UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) +class RWTHVRTOOLKIT_API UBaseBehaviour : public USceneComponent +{ + GENERATED_BODY() + +public: + /** + * Replication part + */ + + /** + * Defines whether this behaviour will be executed on the server instead of the local client. + * If set to true, an RPC is sent to the server and it will not be run locally. + * */ + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bExecuteOnServer = false; + + /** + * Defines whether this behaviour will be executed on all connected clients that are relevant, including the + * local originator client. This only has an effect if bExecuteOnServer is true, as only the server can multicast + * to all other clients. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "bExecuteOnServer")) + bool bExecuteOnAllClients = false; +}; diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactables/GrabBehavior.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/GrabBehavior.h index d10affdff4aa25e2e4c22818219375580f342c8f..65118646b4ca6edae19772cb3d5bf1df875534ee 100644 --- a/Source/RWTHVRToolkit/Public/Interaction/Interactables/GrabBehavior.h +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/GrabBehavior.h @@ -20,14 +20,13 @@ class RWTHVRTOOLKIT_API UGrabBehavior : public UActionBehaviour GENERATED_BODY() public: + UGrabBehavior(); + UPROPERTY(EditAnywhere, Category = "Grabbing") bool bBlockOtherInteractionsWhileGrabbed = true; - virtual void OnActionStart(USceneComponent* TriggeredComponent, const UInputAction* InputAction, - const FInputActionValue& Value) override; - virtual void OnActionEnd(USceneComponent* TriggeredComponent, const UInputAction* InputAction, - const FInputActionValue& Value) override; - + UPROPERTY(EditAnywhere, Category = "Grabbing") + bool bIgnoreGrabbedActorInCollisionMovement = true; /** * Called after the object was successfully attached to the hand @@ -41,13 +40,25 @@ public: UPROPERTY(BlueprintAssignable) FOnGrabEnd OnGrabEndEvent; + UPROPERTY() + UPrimitiveComponent* MyPhysicsComponent; + + virtual void BeginPlay() override; + UPrimitiveComponent* GetFirstComponentSimulatingPhysics(const AActor* TargetActor); // recursively goes up the hierarchy and returns the highest parent simulating physics UPrimitiveComponent* GetHighestParentSimulatingPhysics(UPrimitiveComponent* Comp); - UPROPERTY() - UPrimitiveComponent* MyPhysicsComponent; + UFUNCTION() + void ReplicationOriginaterClientCallback(USceneComponent* TriggerComponent, const EInteractionEventType EventType, + const FInputActionValue& Value); + + void HandleCollisionHandlingMovement(const USceneComponent* CurrentAttachParent, + const EInteractionEventType EventType); + + virtual void OnActionEvent(USceneComponent* TriggerComponent, const EInteractionEventType EventType, + const FInputActionValue& Value) override; UFUNCTION(BlueprintPure) bool IsObjectGrabbed() const { return bObjectGrabbed; } @@ -61,7 +72,13 @@ private: */ bool TryRelease(); + void StartGrab(USceneComponent* TriggerComponent); + + void EndGrab(USceneComponent* TriggerComponent); + bool bObjectGrabbed = false; bool bWasSimulatingPhysics; + + bool bWasAddedToIgnore = false; }; diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactables/HoverBehaviour.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/HoverBehaviour.h index 7557653fbf02dc8d1780bbb32804c583324281ad..efdd6eafd797bf60bc07847565bf216537309358 100644 --- a/Source/RWTHVRToolkit/Public/Interaction/Interactables/HoverBehaviour.h +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/HoverBehaviour.h @@ -3,16 +3,16 @@ #pragma once #include "CoreMinimal.h" +#include "BaseBehaviour.h" +#include "InteractionEventType.h" #include "Components/SceneComponent.h" #include "HoverBehaviour.generated.h" - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHoverStart, const USceneComponent*, TriggeredComponent, FHitResult, - Hit); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHoverEnd, const USceneComponent*, TriggeredComponent); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnHoverEvent, const USceneComponent*, TriggeredComponent, + const EInteractionEventType, EventType, const FHitResult&, Hit); UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) -class RWTHVRTOOLKIT_API UHoverBehaviour : public USceneComponent +class RWTHVRTOOLKIT_API UHoverBehaviour : public UBaseBehaviour { GENERATED_BODY() @@ -22,16 +22,22 @@ public: * VRPawn) Hit: Hit Result of the trace to get access to e.g. contact point/normals etc. */ UPROPERTY(BlueprintAssignable) - FOnHoverStart OnHoverStartEvent; + FOnHoverEvent OnHoverEventEvent; + /** + * Replication specific: + * This is only executed on the local client which processed the interaction and requested the replication process + * to be started. Can be used e.g. for local effects or things that should be done both on the server and local + * client. Broadcast by UInteractableComponent when the originating client sends a server rpc to start the + * interaction replication. + */ UPROPERTY(BlueprintAssignable) - FOnHoverEnd OnHoverEndEvent; + FOnHoverEvent OnHoverReplicationStartedOriginatorEvent; protected: UFUNCTION() - virtual void OnHoverStart(const USceneComponent* TriggeredComponent, FHitResult Hit); - UFUNCTION() - virtual void OnHoverEnd(const USceneComponent* TriggeredComponent); + virtual void OnHoverEvent(const USceneComponent* TriggerComponent, EInteractionEventType EventType, + const FHitResult& Hit); virtual void BeginPlay() override; }; diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractableComponent.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractableComponent.h index da85ad8b003814098fc21e112de423a097a80535..7ef7078b931eebdc43d94932ebe6d615a8a7a442 100644 --- a/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractableComponent.h +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractableComponent.h @@ -9,6 +9,8 @@ #include "InteractableComponent.generated.h" +enum EInteractionEventType : uint8; +class UBaseBehaviour; struct FInputActionValue; class UActionBehaviour; class UHoverBehaviour; @@ -53,7 +55,7 @@ public: bool bAllowInteractionFromChildGeometry = true; UFUNCTION(BlueprintCallable) - FORCEINLINE bool HasInteractionTypeFlag(EInteractorType type) { return type & InteractorFilter; } + FORCEINLINE bool HasInteractionTypeFlag(EInteractorType Type) { return Type & InteractorFilter; } /** * @brief Restrict interactability to given components (e.g. if an object is grabbed, block interactions from other @@ -74,12 +76,10 @@ protected: virtual void BeginPlay() override; public: - void HandleOnHoverStartEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor); - void HandleOnHoverEndEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor); - void HandleOnActionStartEvents(USceneComponent* TriggerComponent, const UInputAction* InputAction, - const FInputActionValue& Value, const EInteractorType Interactor); - void HandleOnActionEndEvents(USceneComponent* TriggerComponent, const UInputAction* InputAction, - const FInputActionValue& Value, const EInteractorType Interactor); + void HandleOnHoverEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor, + const EInteractionEventType EventType); + void HandleOnActionEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor, + const EInteractionEventType EventType, const FInputActionValue& Value); /** * @brief If hover and action behaviors are not explicitly specified, load all existing ones diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractionEventType.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractionEventType.h new file mode 100644 index 0000000000000000000000000000000000000000..9e24c211276613c6ea0e7b11037d59e05caefba0 --- /dev/null +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractionEventType.h @@ -0,0 +1,8 @@ +#pragma once + +UENUM(BlueprintType) +enum EInteractionEventType : uint8 +{ + InteractionStart = 0, + InteractionEnd = 1, +}; diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactors/DirectInteractionComponent.h b/Source/RWTHVRToolkit/Public/Interaction/Interactors/DirectInteractionComponent.h index 69ef74ff844a727c11a333047a7f011a12d01241..46d2d160358d991a70e232bce73bc6c9a57c52ef 100644 --- a/Source/RWTHVRToolkit/Public/Interaction/Interactors/DirectInteractionComponent.h +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactors/DirectInteractionComponent.h @@ -3,13 +3,13 @@ #pragma once #include "CoreMinimal.h" +#include "UBaseInteractionComponent.h" #include "Components/SceneComponent.h" #include "Interaction/Interactables/InteractableComponent.h" -#include "Pawn/InputExtensionInterface.h" #include "DirectInteractionComponent.generated.h" UCLASS(Abstract, Blueprintable) -class RWTHVRTOOLKIT_API UDirectInteractionComponent : public USceneComponent, public IInputExtensionInterface +class RWTHVRTOOLKIT_API UDirectInteractionComponent : public UUBaseInteractionComponent { GENERATED_BODY() @@ -20,29 +20,18 @@ public: virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; - UPROPERTY(EditAnywhere, Category = "Input") - class UInputAction* InteractionInputAction; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Direct Interaction") float InteractionSphereRadius = 15.0; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Direct Interaction") - bool bShowDebugTrace = false; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Direct Interaction") bool bOnlyInteractWithClosestActor = false; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Direct Interaction") - TArray<AActor*> ActorsToIgnore; - virtual void SetupPlayerInput(UInputComponent* PlayerInputComponent) override; private: - UFUNCTION() - void OnBeginInteraction(const FInputActionValue& Value); + virtual void OnBeginInteractionInputAction(const FInputActionValue& Value) override; - UFUNCTION() - void OnEndInteraction(const FInputActionValue& Value); + virtual void OnEndInteractionInputAction(const FInputActionValue& Value) override; UPROPERTY() TArray<UInteractableComponent*> PreviousInteractableComponentsInRange; diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactors/RWTHVRWidgetInteractionComponent.h b/Source/RWTHVRToolkit/Public/Interaction/Interactors/RWTHVRWidgetInteractionComponent.h index 4b1139cf89724faea664f4ac4e2e42f9cf1bb9db..34770d50d324a6f2e311f898636aefc1610a5140 100644 --- a/Source/RWTHVRToolkit/Public/Interaction/Interactors/RWTHVRWidgetInteractionComponent.h +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactors/RWTHVRWidgetInteractionComponent.h @@ -44,14 +44,22 @@ public: TEnumAsByte<EInteractionRayVisibility> InteractionRayVisibility = EInteractionRayVisibility::Invisible; UPROPERTY(EditAnywhere, Category = "Input") - class UInputAction* WidgetClickInputAction; + class UInputAction* WidgetLeftClickInputAction; + UPROPERTY(EditAnywhere, Category = "Input") + class UInputAction* WidgetRightClickInputAction; private: UFUNCTION() - void OnBeginClick(const FInputActionValue& Value); + void OnBeginLeftClick(const FInputActionValue& Value); + + UFUNCTION() + void OnEndLeftClick(const FInputActionValue& Value); + + UFUNCTION() + void OnBeginRightClick(const FInputActionValue& Value); UFUNCTION() - void OnEndClick(const FInputActionValue& Value); + void OnEndRightClick(const FInputActionValue& Value); void CreateInteractionRay(); void SetupInteractionRay(); diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactors/RaycastInteractionComponent.h b/Source/RWTHVRToolkit/Public/Interaction/Interactors/RaycastInteractionComponent.h index f5c93123e8ba136e747796825f93309b285109b0..31441d516c5a34923c2ca6d26665a50a9f6069ed 100644 --- a/Source/RWTHVRToolkit/Public/Interaction/Interactors/RaycastInteractionComponent.h +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactors/RaycastInteractionComponent.h @@ -2,16 +2,15 @@ #pragma once -#include <Pawn/InputExtensionInterface.h> - #include "CoreMinimal.h" +#include "UBaseInteractionComponent.h" #include "Components/SceneComponent.h" #include "Interaction/Interactables/InteractableComponent.h" #include "RaycastInteractionComponent.generated.h" UCLASS(Abstract, Blueprintable) -class RWTHVRTOOLKIT_API URaycastInteractionComponent : public USceneComponent, public IInputExtensionInterface +class RWTHVRTOOLKIT_API URaycastInteractionComponent : public UUBaseInteractionComponent { GENERATED_BODY() @@ -23,20 +22,13 @@ public: virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; - UPROPERTY(EditAnywhere, Category = "Input") - class UInputAction* InteractionInputAction; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Raycast") float TraceLength = 3000.0; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Raycast") - bool bShowDebugTrace = false; private: - UFUNCTION() - void OnBeginInteraction(const FInputActionValue& Value); + virtual void OnBeginInteractionInputAction(const FInputActionValue& Value) override; - UFUNCTION() - void OnEndInteraction(const FInputActionValue& Value); + virtual void OnEndInteractionInputAction(const FInputActionValue& Value) override; public: virtual void SetupPlayerInput(UInputComponent* PlayerInputComponent) override; diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactors/UBaseInteractionComponent.h b/Source/RWTHVRToolkit/Public/Interaction/Interactors/UBaseInteractionComponent.h new file mode 100644 index 0000000000000000000000000000000000000000..ca97fd7e36f1359273734f7b88da77777fdfa7b0 --- /dev/null +++ b/Source/RWTHVRToolkit/Public/Interaction/Interactors/UBaseInteractionComponent.h @@ -0,0 +1,103 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/SceneComponent.h" +#include "Pawn/InputExtensionInterface.h" +#include "UBaseInteractionComponent.generated.h" + + +struct FInputActionValue; +enum EInteractionEventType : uint8; +class UActionBehaviour; +class UHoverBehaviour; + +UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) +class RWTHVRTOOLKIT_API UUBaseInteractionComponent : public USceneComponent, public IInputExtensionInterface +{ + GENERATED_BODY() + +public: + // Sets default values for this component's properties + UUBaseInteractionComponent(); + + virtual void SetupPlayerInput(UInputComponent* PlayerInputComponent) override; + + UPROPERTY(EditAnywhere, Category = "Input") + class UInputAction* InteractionInputAction; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Base Interaction") + TArray<AActor*> ActorsToIgnore; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Base Interaction") + bool bShowDebugTrace = false; + + /* + * Replication part + */ + + + /** + * Requests the start of the replication process for the given HoverBehaviour, EventType and Hit. + * Depending on authority, this executes the replication of the behaviour directly or requests it via a server RPC. + */ + void RequestHoverBehaviourReplicationStart(UHoverBehaviour* Behaviour, const EInteractionEventType EventType, + const FHitResult& Hit); + + /** + * This is executed on the server/authority. The behaviour is actually executed directly on the server, or + * multicast to all connected clients. The multicast then executes the behaviour. + */ + void HoverBehaviourReplicationStart(UHoverBehaviour* Behaviour, const EInteractionEventType EventType, + const FHitResult& Hit); + + /** + * This is only executed on the local client which processed the interaction and requested the replication process + * to be started. Can be used e.g. for local effects or things that should be done both on the server and local + * client. + */ + void HoverBehaviourReplicationOriginatorCallback(UHoverBehaviour* Behaviour, const EInteractionEventType EventType, + const FHitResult& Hit); + + /** + * Requests the start of the replication process for the given ActionBehaviour, EventType and the Value of the Input + * Action. Depending on authority, this executes the replication of the behaviour directly or requests it via a + * server RPC. + */ + void RequestActionBehaviourReplicationStart(UActionBehaviour* Behaviour, const EInteractionEventType EventType, + const FInputActionValue& Value); + + /** + * This is executed on the server/authority. The behaviour is actually executed directly on the server, or + * multicast to all connected clients. The multicast then executes the behaviour. + */ + void ActionBehaviourReplicationStart(UActionBehaviour* Behaviour, const EInteractionEventType EventType, + const FInputActionValue& Value); + + + // RPCs + UFUNCTION(Server, Reliable) + void ServerRequestHoverBehaviourReplicationStartRpc(UHoverBehaviour* Behaviour, + const EInteractionEventType EventType, const FHitResult& Hit); + + UFUNCTION(NetMulticast, Reliable) + void MulticastHoverBehaviourReplicationStartRpc(UHoverBehaviour* Behaviour, const EInteractionEventType EventType, + const FHitResult& Hit); + + UFUNCTION(Server, Reliable) + void ServerRequestActionBehaviourReplicationStartRpc(UActionBehaviour* Behaviour, + const EInteractionEventType EventType, + const FInputActionValue& Value); + + UFUNCTION(NetMulticast, Reliable) + void MulticastActionBehaviourReplicationStartRpc(UActionBehaviour* Behaviour, const EInteractionEventType EventType, + const FInputActionValue& Value); + +private: + UFUNCTION() + virtual void OnBeginInteractionInputAction(const FInputActionValue& Value); + + UFUNCTION() + virtual void OnEndInteractionInputAction(const FInputActionValue& Value); +}; diff --git a/Source/RWTHVRToolkit/Public/Pawn/ClusterRepresentationActor.h b/Source/RWTHVRToolkit/Public/Pawn/ClusterRepresentationActor.h new file mode 100644 index 0000000000000000000000000000000000000000..7826cb001ff5b5ea035f3e6b96920c3edb215138 --- /dev/null +++ b/Source/RWTHVRToolkit/Public/Pawn/ClusterRepresentationActor.h @@ -0,0 +1,36 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" + +#include "ClusterRepresentationActor.generated.h" + + +class ARWTHVRPlayerState; +#if PLATFORM_SUPPORTS_CLUSTER +class ADisplayClusterRootActor; +#endif + +UCLASS() +class RWTHVRTOOLKIT_API AClusterRepresentationActor : public AActor +{ + GENERATED_BODY() + +public: + // Sets default values for this actor's properties + AClusterRepresentationActor(); + + virtual void BeginPlay() override; + + void AttachDCRAIfRequired(const ARWTHVRPlayerState* OptionalPlayerState = nullptr); + +private: + bool bIsAttached = false; + +#if PLATFORM_SUPPORTS_CLUSTER + bool AttachDCRA(); + ADisplayClusterRootActor* SpawnDCRA(); + +#endif +}; diff --git a/Source/RWTHVRToolkit/Public/Pawn/Navigation/CollisionHandlingMovement.h b/Source/RWTHVRToolkit/Public/Pawn/Navigation/CollisionHandlingMovement.h index 6166c84a1c6f2aee1adfdd2d7b5080ac027d23cd..b9cfad134d1b57e12d6be08a82b56935f3d4674b 100644 --- a/Source/RWTHVRToolkit/Public/Pawn/Navigation/CollisionHandlingMovement.h +++ b/Source/RWTHVRToolkit/Public/Pawn/Navigation/CollisionHandlingMovement.h @@ -42,6 +42,12 @@ public: void SetHeadComponent(USceneComponent* NewHeadComponent); + UFUNCTION(BlueprintCallable) + bool AddActorToIgnore(AActor* ActorToIgnore); + + UFUNCTION(BlueprintCallable) + bool RemoveActorFromIgnore(AActor* ActorToIgnore); + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VR Movement") EVRNavigationModes NavigationMode = EVRNavigationModes::NAV_WALK; diff --git a/Source/RWTHVRToolkit/Public/Pawn/Navigation/TurnComponent.h b/Source/RWTHVRToolkit/Public/Pawn/Navigation/TurnComponent.h index 96de1a8b08b770ad7716839aae2ce138dee93321..72cbaf558d17f75beea4826690f05324b802b8bf 100644 --- a/Source/RWTHVRToolkit/Public/Pawn/Navigation/TurnComponent.h +++ b/Source/RWTHVRToolkit/Public/Pawn/Navigation/TurnComponent.h @@ -35,10 +35,13 @@ public: float SnapTurnAngle = 22.5; UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VR Movement|Input|Actions") - class UInputAction* Turn; + class UInputAction* XRTurn; UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VR Movement|Input|Actions") - class UInputAction* DesktopRotation; + class UInputAction* DesktopTurn; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VR Movement|Input|Actions") + class UInputAction* DesktopTurnCondition; /** * Called every tick as long as stick input is received to allow for continuous turning diff --git a/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h b/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h index 260cdc0408e5f2d1ef9d64c7f56ba4325a8ff123..cd81e497181dcfef757b067ccae8c862c022fa34 100644 --- a/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h +++ b/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h @@ -79,10 +79,6 @@ public: UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Pawn|LiveLink") bool bWorldTransform = false; - /* The class which to search for DCRA attachment. TODO: Make this better it's ugly */ - UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Pawn|LiveLink") - TSubclassOf<AActor> CaveSetupActorClass; - protected: virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override; void AddInputMappingContext(const APlayerController* PC, const UInputMappingContext* Context) const; @@ -107,8 +103,8 @@ protected: UFUNCTION(Reliable, NetMulticast) void MulticastAddDCSyncComponent(); - /* Attaches the DCRA to the pawn */ - void AttachDCRAtoPawn(); + /* Attaches the Cluster representation to the pawn */ + void AttachClustertoPawn(); /* Set device specific motion controller sources (None, L/R, Livelink) */ void SetupMotionControllerSources();