diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/GrabBehavior.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/GrabBehavior.cpp index 7afd6522eda1b041118e012dd46b9946851a1133..da27902821fc1a16eeaabfa5b959d82eff6d3a9d 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 0a0df846378be8f16af065b039f0f839a6950f62..4f1c256218355ce5c142d010bcc3a460491f08f8 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 b565b58a43154ece41c681a371434770e5171d60..454d65015669bf9d1e6b1b480f2199862579cc44 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 96774874b6e7282348655c04f7f8256b69f7747d..bf3afea3a1246994a0bcbfb1d96317083e2770e3 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 586477d774f0599d1638df6bb256f50cac4788dd..ba787527e2c9976069613d1e38e377c1a8791ccc 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 3571440f96bf16d20eb976d5fa359bb4d5d43b4a..efdd6eafd797bf60bc07847565bf216537309358 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 327609c8229758f07531f96c59a67e8cb08b56d6..ef7f971164e1cc6301afadc5b3d3e9f61fe2403a 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 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;