From 0d5f5bf71a15b40f111d91d1739b399cfe366707 Mon Sep 17 00:00:00 2001
From: David Gilbert <gilbert@vr.rwth-aachen.de>
Date: Tue, 23 Jul 2024 15:21:59 +0200
Subject: [PATCH] feature(interaction, replication): Adds first draft of a
replicated GrabBehaviour.
---
.../Interactables/GrabBehavior.cpp | 53 ++++++++
.../Interactables/InteractableComponent.cpp | 6 +
.../Navigation/CollisionHandlingMovement.cpp | 118 ++++++++++++------
.../Interactables/ActionBehaviour.h | 11 ++
.../Interaction/Interactables/GrabBehavior.h | 26 +++-
.../Interactables/HoverBehaviour.h | 10 ++
.../Interactors/UBaseInteractionComponent.h | 27 ++++
.../Navigation/CollisionHandlingMovement.h | 6 +
8 files changed, 212 insertions(+), 45 deletions(-)
diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/GrabBehavior.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/GrabBehavior.cpp
index 7afd6522..da279028 100644
--- a/Source/RWTHVRToolkit/Private/Interaction/Interactables/GrabBehavior.cpp
+++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/GrabBehavior.cpp
@@ -5,9 +5,24 @@
#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;
@@ -34,7 +49,38 @@ 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::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)
{
@@ -52,6 +98,7 @@ bool UGrabBehavior::TryRelease()
{
if (!bObjectGrabbed)
{
+ UE_LOGFMT(Toolkit, Display, "UGrabBehavior::TryRelease: bObjectGrabbed was false!");
return false;
}
@@ -109,6 +156,9 @@ void UGrabBehavior::StartGrab(USceneComponent* TriggerComponent)
}
OnGrabStartEvent.Broadcast(CurrentAttachParent, MyPhysicsComponent);
+
+ // Add to ignore list for collision handling movement, if it exists
+ HandleCollisionHandlingMovement(CurrentAttachParent, InteractionStart);
}
void UGrabBehavior::EndGrab(USceneComponent* TriggerComponent)
@@ -136,4 +186,7 @@ void UGrabBehavior::EndGrab(USceneComponent* TriggerComponent)
Interactable->ResetRestrictInteraction();
}
}
+
+ // If our attach parent has a collision handling component, remove
+ HandleCollisionHandlingMovement(CurrentAttachParent, InteractionEnd);
}
diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/InteractableComponent.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/InteractableComponent.cpp
index 0a0df846..4f1c2562 100644
--- a/Source/RWTHVRToolkit/Private/Interaction/Interactables/InteractableComponent.cpp
+++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/InteractableComponent.cpp
@@ -74,6 +74,9 @@ void UInteractableComponent::HandleOnHoverEvents(USceneComponent* TriggerCompone
return;
}
InteractorComponent->RequestHoverBehaviourReplicationStart(HoverBehaviour, EventType, HitResult);
+
+ // Broadcast local callback
+ HoverBehaviour->OnHoverReplicationStartedOriginatorEvent.Broadcast(TriggerComponent, EventType, HitResult);
}
else if (HoverBehaviour->bExecuteOnAllClients)
{
@@ -122,6 +125,9 @@ void UInteractableComponent::HandleOnActionEvents(USceneComponent* TriggerCompon
return;
}
InteractorComponent->RequestActionBehaviourReplicationStart(ActionBehaviour, EventType, Value);
+
+ // Broadcast local callback
+ ActionBehaviour->OnActionReplicationStartedOriginatorEvent.Broadcast(TriggerComponent, EventType, Value);
}
else if (ActionBehaviour->bExecuteOnAllClients)
{
diff --git a/Source/RWTHVRToolkit/Private/Pawn/Navigation/CollisionHandlingMovement.cpp b/Source/RWTHVRToolkit/Private/Pawn/Navigation/CollisionHandlingMovement.cpp
index b565b58a..454d6501 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,69 @@ void UCollisionHandlingMovement::BeginPlay()
void UCollisionHandlingMovement::TickComponent(float DeltaTime, enum ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
- SetCapsuleColliderToUserSize();
- FVector InputVector = GetPendingInputVector();
+ if (ShouldSkipUpdate(DeltaTime))
+ return;
- if (NavigationMode == EVRNavigationModes::NAV_WALK)
+ const AController* Controller = PawnOwner->GetController();
+ if (Controller && Controller->IsLocalController())
{
- // you are only allowed to move horizontally in NAV_WALK
- // everything else will be handled by stepping-up/gravity
- // so remove Z component for the input vector of the UFloatingPawnMovement
- InputVector.Z = 0.0f;
- ConsumeInputVector();
- AddInputVector(InputVector);
- }
+ SetCapsuleColliderToUserSize();
+ FVector InputVector = GetPendingInputVector();
+
+ if (NavigationMode == EVRNavigationModes::NAV_WALK)
+ {
+ // you are only allowed to move horizontally in NAV_WALK
+ // everything else will be handled by stepping-up/gravity
+ // so remove Z component for the input vector of the UFloatingPawnMovement
+ InputVector.Z = 0.0f;
+ 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)
+ if (NavigationMode == EVRNavigationModes::NAV_FLY || NavigationMode == EVRNavigationModes::NAV_WALK)
{
- const FVector SafeSteeringInput = GetCollisionSafeVirtualSteeringVec(InputVector, DeltaTime);
- if (SafeSteeringInput != InputVector)
+ // 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, we only allow physical
- // movement without any checks, otherwise check collision during physical movement
- if (bCollisionChecksTemporarilyDeactivated)
+ if (NavigationMode == EVRNavigationModes::NAV_NONE)
{
+ // just remove whatever input is there
ConsumeInputVector();
}
- 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();
- }
- }
-
- if (NavigationMode == EVRNavigationModes::NAV_NONE)
- {
- // just remove whatever input is there
- ConsumeInputVector();
}
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
@@ -102,6 +112,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/Public/Interaction/Interactables/ActionBehaviour.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/ActionBehaviour.h
index 96774874..bf3afea3 100644
--- a/Source/RWTHVRToolkit/Public/Interaction/Interactables/ActionBehaviour.h
+++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/ActionBehaviour.h
@@ -25,6 +25,17 @@ public:
UPROPERTY(BlueprintAssignable)
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)
+ FOnActionBegin OnActionReplicationStartedOriginatorEvent;
+
+
protected:
UFUNCTION()
virtual void OnActionEvent(USceneComponent* TriggerComponent, const EInteractionEventType EventType,
diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactables/GrabBehavior.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/GrabBehavior.h
index 586477d7..ba787527 100644
--- a/Source/RWTHVRToolkit/Public/Interaction/Interactables/GrabBehavior.h
+++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/GrabBehavior.h
@@ -20,12 +20,15 @@ class RWTHVRTOOLKIT_API UGrabBehavior : public UActionBehaviour
GENERATED_BODY()
public:
+
+ UGrabBehavior();
+
UPROPERTY(EditAnywhere, Category = "Grabbing")
bool bBlockOtherInteractionsWhileGrabbed = true;
- virtual void OnActionEvent(USceneComponent* TriggerComponent, const EInteractionEventType EventType,
- const FInputActionValue& Value) override;
-
+ UPROPERTY(EditAnywhere, Category = "Grabbing")
+ bool bIgnoreGrabbedActorInCollisionMovement = true;
+
/**
* Called after the object was successfully attached to the hand
*/
@@ -37,15 +40,26 @@ 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; }
@@ -65,4 +79,6 @@ private:
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 3571440f..efdd6eaf 100644
--- a/Source/RWTHVRToolkit/Public/Interaction/Interactables/HoverBehaviour.h
+++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/HoverBehaviour.h
@@ -24,6 +24,16 @@ public:
UPROPERTY(BlueprintAssignable)
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)
+ FOnHoverEvent OnHoverReplicationStartedOriginatorEvent;
+
protected:
UFUNCTION()
virtual void OnHoverEvent(const USceneComponent* TriggerComponent, EInteractionEventType EventType,
diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactors/UBaseInteractionComponent.h b/Source/RWTHVRToolkit/Public/Interaction/Interactors/UBaseInteractionComponent.h
index 327609c8..ef7f9711 100644
--- a/Source/RWTHVRToolkit/Public/Interaction/Interactors/UBaseInteractionComponent.h
+++ b/Source/RWTHVRToolkit/Public/Interaction/Interactors/UBaseInteractionComponent.h
@@ -37,15 +37,42 @@ public:
* 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)
diff --git a/Source/RWTHVRToolkit/Public/Pawn/Navigation/CollisionHandlingMovement.h b/Source/RWTHVRToolkit/Public/Pawn/Navigation/CollisionHandlingMovement.h
index 6166c84a..b9cfad13 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;
--
GitLab