diff --git a/Content/BP_VirtualRealityPawn.uasset b/Content/BP_VirtualRealityPawn.uasset index 0a1470567c99b3bc47a7d853c939cb662acb6571..59b1068d003c4eb9241cce12fea142e357f9a6c6 100644 Binary files a/Content/BP_VirtualRealityPawn.uasset and b/Content/BP_VirtualRealityPawn.uasset differ diff --git a/Content/Components/Movement/Teleportation/VRTeleportVisualizer.uasset b/Content/Components/Movement/Teleportation/VRTeleportVisualizer.uasset index 8391f7295fbb35e9cb7d8172853ebd0393e5714f..9f96db1fd710b54a94b08f539a568ba1e7358781 100644 Binary files a/Content/Components/Movement/Teleportation/VRTeleportVisualizer.uasset and b/Content/Components/Movement/Teleportation/VRTeleportVisualizer.uasset differ diff --git a/Content/RWTHVRCluster/CaveSetup.uasset b/Content/RWTHVRCluster/CaveSetup.uasset new file mode 100644 index 0000000000000000000000000000000000000000..e31ab31976b8e9e40030c8b9fdbf04b50a11114a Binary files /dev/null and b/Content/RWTHVRCluster/CaveSetup.uasset differ diff --git a/Content/RWTHVRCluster/Config/ExampleConfigs/NDC_CaveUnwrap.ndisplay b/Content/RWTHVRCluster/Config/ExampleConfigs/NDC_CaveUnwrap.ndisplay new file mode 100644 index 0000000000000000000000000000000000000000..b3a186da5f2e294797965d3fd53eead5b74767e2 --- /dev/null +++ b/Content/RWTHVRCluster/Config/ExampleConfigs/NDC_CaveUnwrap.ndisplay @@ -0,0 +1,677 @@ +{ + "nDisplay": + { + "description": "nDisplay configuration", + "version": "5.00", + "assetPath": "/RWTHVRToolkit/RWTHVRCluster/Config/ExampleConfigs/NDC_CaveUnwrap.NDC_CaveUnwrap", + "misc": + { + "bFollowLocalPlayerCamera": false, + "bExitOnEsc": true, + "bOverrideViewportsFromExternalConfig": false + }, + "scene": + { + "xforms": + { + "cave_origin": + { + "parentId": "", + "location": + { + "x": 0, + "y": 0, + "z": 10 + }, + "rotation": + { + "pitch": 0, + "yaw": 0, + "roll": 0 + } + }, + "cave_center": + { + "parentId": "cave_origin", + "location": + { + "x": 0, + "y": 0, + "z": 100 + }, + "rotation": + { + "pitch": 0, + "yaw": 0, + "roll": 0 + } + }, + "display_front": + { + "parentId": "cave_center", + "location": + { + "x": 150, + "y": 0, + "z": 0 + }, + "rotation": + { + "pitch": 0, + "yaw": 0, + "roll": 0 + } + }, + "display_floor": + { + "parentId": "cave_center", + "location": + { + "x": 0, + "y": 0, + "z": -100 + }, + "rotation": + { + "pitch": -90, + "yaw": 0, + "roll": 0 + } + }, + "angle_left": + { + "parentId": "cave_center", + "location": + { + "x": 150, + "y": -150, + "z": 0 + }, + "rotation": + { + "pitch": 0, + "yaw": -89.999992370605469, + "roll": 0 + } + }, + "display_left": + { + "parentId": "angle_left", + "location": + { + "x": -1.52587890625e-05, + "y": -149.99996948242188, + "z": 0 + }, + "rotation": + { + "pitch": 0, + "yaw": 0, + "roll": 0 + } + }, + "angle_right": + { + "parentId": "cave_center", + "location": + { + "x": 150, + "y": 150, + "z": 0 + }, + "rotation": + { + "pitch": 0, + "yaw": 89.999992370605469, + "roll": 0 + } + }, + "display_right": + { + "parentId": "angle_right", + "location": + { + "x": 0, + "y": 150, + "z": 0 + }, + "rotation": + { + "pitch": 0, + "yaw": 0, + "roll": 0 + } + }, + "angle_ceiling": + { + "parentId": "cave_center", + "location": + { + "x": 150, + "y": 0, + "z": 100 + }, + "rotation": + { + "pitch": 90, + "yaw": 0, + "roll": 0 + } + }, + "display_ceiling": + { + "parentId": "angle_ceiling", + "location": + { + "x": -7.62939453125e-06, + "y": 0, + "z": 74.999984741210938 + }, + "rotation": + { + "pitch": 0, + "yaw": 0, + "roll": 0 + } + } + }, + "cameras": + { + "DefaultViewPoint": + { + "interpupillaryDistance": 6.4000000953674316, + "swapEyes": false, + "stereoOffset": "none", + "parentId": "", + "location": + { + "x": 0, + "y": 0, + "z": 50 + }, + "rotation": + { + "pitch": 0, + "yaw": 0, + "roll": 0 + } + } + }, + "screens": + { + "scr_left": + { + "size": + { + "width": 300, + "height": 200 + }, + "parentId": "display_left", + "location": + { + "x": 0, + "y": 0, + "z": 0 + }, + "rotation": + { + "pitch": 0, + "yaw": 0, + "roll": 0 + } + }, + "scr_front": + { + "size": + { + "width": 300, + "height": 200 + }, + "parentId": "display_front", + "location": + { + "x": 0, + "y": 0, + "z": 0 + }, + "rotation": + { + "pitch": 0, + "yaw": 0, + "roll": 0 + } + }, + "scr_right": + { + "size": + { + "width": 300, + "height": 200 + }, + "parentId": "display_right", + "location": + { + "x": 0, + "y": 0, + "z": 0 + }, + "rotation": + { + "pitch": 0, + "yaw": 0, + "roll": 0 + } + }, + "scr_ceiling": + { + "size": + { + "width": 300, + "height": 150 + }, + "parentId": "display_ceiling", + "location": + { + "x": 0, + "y": 0, + "z": 0 + }, + "rotation": + { + "pitch": 0, + "yaw": 0, + "roll": 0 + } + }, + "scr_floor": + { + "size": + { + "width": 300, + "height": 300 + }, + "parentId": "display_floor", + "location": + { + "x": 0, + "y": 0, + "z": 0 + }, + "rotation": + { + "pitch": 0, + "yaw": 0, + "roll": 0 + } + } + } + }, + "cluster": + { + "primaryNode": + { + "id": "node_left", + "ports": + { + "ClusterSync": 41001, + "ClusterEventsJson": 41003, + "ClusterEventsBinary": 41004 + } + }, + "sync": + { + "renderSyncPolicy": + { + "type": "ethernet", + "parameters": + { + } + }, + "inputSyncPolicy": + { + "type": "ReplicatePrimary", + "parameters": + { + } + } + }, + "network": + { + "ConnectRetriesAmount": "300", + "ConnectRetryDelay": "1000", + "GameStartBarrierTimeout": "18000000", + "FrameStartBarrierTimeout": "1800000", + "FrameEndBarrierTimeout": "1800000", + "RenderSyncBarrierTimeout": "1800000" + }, + "failover": + { + "failoverPolicy": "Disabled" + }, + "nodes": + { + "node_left": + { + "host": "127.0.0.1", + "sound": true, + "fullScreen": false, + "renderHeadless": false, + "graphicsAdapter": -1, + "textureShare": false, + "window": + { + "x": 0, + "y": 300, + "w": 600, + "h": 400 + }, + "postprocess": + { + }, + "viewports": + { + "vp_left": + { + "camera": "DefaultViewPoint", + "bufferRatio": 1, + "gPUIndex": -1, + "allowCrossGPUTransfer": false, + "isShared": false, + "overscan": + { + "bEnabled": false, + "mode": "percent", + "left": 0, + "right": 0, + "top": 0, + "bottom": 0, + "oversize": true + }, + "region": + { + "x": 0, + "y": 0, + "w": 600, + "h": 400 + }, + "projectionPolicy": + { + "type": "simple", + "parameters": + { + "screen": "scr_left" + } + } + } + }, + "outputRemap": + { + "bEnable": false, + "dataSource": "mesh", + "staticMeshAsset": "", + "externalFile": "" + } + }, + "node_front": + { + "host": "134.130.70.51", + "sound": false, + "fullScreen": false, + "renderHeadless": false, + "graphicsAdapter": -1, + "textureShare": false, + "window": + { + "x": 0, + "y": 0, + "w": 600, + "h": 400 + }, + "postprocess": + { + }, + "viewports": + { + "vp_front": + { + "camera": "DefaultViewPoint", + "bufferRatio": 1, + "gPUIndex": -1, + "allowCrossGPUTransfer": false, + "isShared": false, + "overscan": + { + "bEnabled": false, + "mode": "percent", + "left": 0, + "right": 0, + "top": 0, + "bottom": 0, + "oversize": true + }, + "region": + { + "x": 0, + "y": 0, + "w": 600, + "h": 400 + }, + "projectionPolicy": + { + "type": "simple", + "parameters": + { + "screen": "scr_front" + } + } + } + }, + "outputRemap": + { + "bEnable": false, + "dataSource": "mesh", + "staticMeshAsset": "", + "externalFile": "" + } + }, + "node_right": + { + "host": "127.0.0.1", + "sound": false, + "fullScreen": false, + "renderHeadless": false, + "graphicsAdapter": -1, + "textureShare": false, + "window": + { + "x": 600, + "y": 900, + "w": 600, + "h": 400 + }, + "postprocess": + { + }, + "viewports": + { + "vp_right": + { + "camera": "DefaultViewPoint", + "bufferRatio": 1, + "gPUIndex": -1, + "allowCrossGPUTransfer": false, + "isShared": false, + "overscan": + { + "bEnabled": false, + "mode": "percent", + "left": 0, + "right": 0, + "top": 0, + "bottom": 0, + "oversize": true + }, + "region": + { + "x": 0, + "y": 0, + "w": 600, + "h": 400 + }, + "projectionPolicy": + { + "type": "simple", + "parameters": + { + "screen": "scr_right" + } + } + } + }, + "outputRemap": + { + "bEnable": false, + "dataSource": "mesh", + "staticMeshAsset": "", + "externalFile": "" + } + }, + "node_ceiling": + { + "host": "127.0.0.1", + "sound": false, + "fullScreen": false, + "renderHeadless": false, + "graphicsAdapter": -1, + "textureShare": false, + "window": + { + "x": 600, + "y": 0, + "w": 600, + "h": 300 + }, + "postprocess": + { + }, + "viewports": + { + "vp_ceiling": + { + "camera": "DefaultViewPoint", + "bufferRatio": 1, + "gPUIndex": -1, + "allowCrossGPUTransfer": false, + "isShared": false, + "overscan": + { + "bEnabled": false, + "mode": "percent", + "left": 0, + "right": 0, + "top": 0, + "bottom": 0, + "oversize": true + }, + "region": + { + "x": 0, + "y": 0, + "w": 600, + "h": 300 + }, + "projectionPolicy": + { + "type": "simple", + "parameters": + { + "screen": "scr_ceiling" + } + } + } + }, + "outputRemap": + { + "bEnable": false, + "dataSource": "mesh", + "staticMeshAsset": "", + "externalFile": "" + } + }, + "node_floor": + { + "host": "127.0.0.1", + "sound": false, + "fullScreen": false, + "renderHeadless": false, + "graphicsAdapter": -1, + "textureShare": false, + "window": + { + "x": 600, + "y": 300, + "w": 600, + "h": 600 + }, + "postprocess": + { + }, + "viewports": + { + "vp_floor": + { + "camera": "DefaultViewPoint", + "bufferRatio": 1, + "gPUIndex": -1, + "allowCrossGPUTransfer": false, + "isShared": false, + "overscan": + { + "bEnabled": false, + "mode": "percent", + "left": 0, + "right": 0, + "top": 0, + "bottom": 0, + "oversize": true + }, + "region": + { + "x": 0, + "y": 0, + "w": 600, + "h": 600 + }, + "projectionPolicy": + { + "type": "simple", + "parameters": + { + "screen": "scr_floor" + } + } + } + }, + "outputRemap": + { + "bEnable": false, + "dataSource": "mesh", + "staticMeshAsset": "", + "externalFile": "" + } + } + } + }, + "customParameters": + { + "SampleArg1": "SampleVal1", + "SampleArg2": "SampleVal2" + }, + "diagnostics": + { + "simulateLag": false, + "minLagTime": 0.0099999997764825821, + "maxLagTime": 0.5 + } + } +} \ No newline at end of file diff --git a/Content/RWTHVRCluster/Config/aixcave.uasset b/Content/RWTHVRCluster/Config/aixcave.uasset index f2fa965197363c13ea349b2606d6fec0fe2281fa..68bfe315f998e4497f1f030a78d46c6640b7295a 100644 Binary files a/Content/RWTHVRCluster/Config/aixcave.uasset and b/Content/RWTHVRCluster/Config/aixcave.uasset differ diff --git a/Content/RWTHVRGameMode.uasset b/Content/RWTHVRGameMode.uasset index bbe367b340d7b98310000099bb7ffa8e3e088b0f..98183e89f709fbd7dbeb90756e70a0a14c67982d 100644 Binary files a/Content/RWTHVRGameMode.uasset and b/Content/RWTHVRGameMode.uasset differ diff --git a/Source/RWTHVRToolkit/Private/Core/ClientTransformReplication.cpp b/Source/RWTHVRToolkit/Private/Core/ClientTransformReplication.cpp new file mode 100644 index 0000000000000000000000000000000000000000..27a4cc4afd522ae67fd9c50107a2066980e0da08 --- /dev/null +++ b/Source/RWTHVRToolkit/Private/Core/ClientTransformReplication.cpp @@ -0,0 +1,78 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "Core/ClientTransformReplication.h" + +#include "Net/UnrealNetwork.h" + +UClientTransformReplication::UClientTransformReplication() +{ + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.TickGroup = TG_PostUpdateWork; + PrimaryComponentTick.SetTickFunctionEnable(true); + SetIsReplicatedByDefault(true); + + // Direct transform replication + ControllerNetUpdateRate = 100.0f; // 100 htz is default + ControllerNetUpdateCount = 0.0f; +} + +// Naive direct transform replication (replace with input rep?) +void UClientTransformReplication::UpdateState(float DeltaTime) +{ + const auto* OwningActor = GetOwner(); + if (OwningActor && OwningActor->HasLocalNetOwner()) + { + if (GetIsReplicated()) + { + const FVector Loc = OwningActor->GetActorLocation(); + const FRotator Rot = OwningActor->GetActorRotation(); + + if (!Loc.Equals(ReplicatedTransform.Position) || !Rot.Equals(ReplicatedTransform.Rotation)) + { + ControllerNetUpdateCount += DeltaTime; + if (ControllerNetUpdateCount >= (1.0f / ControllerNetUpdateRate)) // todo save inverse? + { + ControllerNetUpdateCount = 0.0f; + + ReplicatedTransform.Position = Loc; + ReplicatedTransform.Rotation = Rot; + if (GetNetMode() == NM_Client) // why do we differentiate here between netmode and authority? + { + SendControllerTransform_ServerRpc(ReplicatedTransform); + } + } + } + } + } +} + +void UClientTransformReplication::TickComponent(float DeltaTime, ELevelTick TickType, + FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + UpdateState(DeltaTime); +} + +void UClientTransformReplication::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty >& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + // Skipping the owner with this as the owner will use the controllers location directly + DOREPLIFETIME_CONDITION(UClientTransformReplication, ReplicatedTransform, COND_SkipOwner); + DOREPLIFETIME(UClientTransformReplication, ControllerNetUpdateRate); +} + +void UClientTransformReplication::SendControllerTransform_ServerRpc_Implementation(FVRTransformRep NewTransform) +{ + // Store new transform and trigger OnRep_Function + ReplicatedTransform = NewTransform; + + if (!GetOwner()->HasLocalNetOwner()) + OnRep_ReplicatedTransform(); +} + +bool UClientTransformReplication::SendControllerTransform_ServerRpc_Validate(FVRTransformRep NewTransform) +{ + return true; + // Optionally check to make sure that player is inside of their bounds and deny it if they aren't? +} \ No newline at end of file diff --git a/Source/RWTHVRToolkit/Private/Core/RWTHVRGameModeBase.cpp b/Source/RWTHVRToolkit/Private/Core/RWTHVRGameModeBase.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bb25b4095a8f707d54de38852acb5dcad5ed6d9a --- /dev/null +++ b/Source/RWTHVRToolkit/Private/Core/RWTHVRGameModeBase.cpp @@ -0,0 +1,41 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Core/RWTHVRGameModeBase.h" + +#include "Core/RWTHVRPlayerState.h" +#include "Kismet/GameplayStatics.h" +#include "Logging/StructuredLog.h" + + +FString ARWTHVRGameModeBase::InitNewPlayer(APlayerController* NewPlayerController, const FUniqueNetIdRepl& UniqueId, + const FString& Options, const FString& Portal) +{ + // Used by the DisplayClusterNetDriver/Connection to handshake nodes. Could use their types directly + // but I don't really want to introduce a hard dependency here. + const FString NodeNameKey = "node"; + const FString PrimaryNodeIdKey = "PrimaryNodeId"; + + // Check if we're using our custom PlayerState so that we can save the player type there. + // If not, just ingore all related args. + ARWTHVRPlayerState* State = Cast<ARWTHVRPlayerState>(NewPlayerController->PlayerState); + + if (State != nullptr) + { + if (UGameplayStatics::HasOption(Options, PrimaryNodeIdKey)) + { + const FString PrimaryNodeId = UGameplayStatics::ParseOption(Options, PrimaryNodeIdKey); + + // When the primary node is a listen server, it apparently doesn't get the node option... + // Could additionally check for listen, but this should be save enough. + const FString NodeName = UGameplayStatics::HasOption(Options, NodeNameKey) + ? UGameplayStatics::ParseOption(Options, NodeNameKey) + : PrimaryNodeId; + + const EPlayerType Type = NodeName == PrimaryNodeId ? EPlayerType::nDisplayPrimary : EPlayerType::nDisplaySecondary; + State->RequestSetPlayerType(Type); + } + } + + return Super::InitNewPlayer(NewPlayerController, UniqueId, Options, Portal); +} diff --git a/Source/RWTHVRToolkit/Private/Core/RWTHVRPlayerState.cpp b/Source/RWTHVRToolkit/Private/Core/RWTHVRPlayerState.cpp new file mode 100644 index 0000000000000000000000000000000000000000..be852ba79babb86064ec6bc5d7f0665136f15740 --- /dev/null +++ b/Source/RWTHVRToolkit/Private/Core/RWTHVRPlayerState.cpp @@ -0,0 +1,71 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Core/RWTHVRPlayerState.h" + +#include "Net/UnrealNetwork.h" +#include "Net/Core/PushModel/PushModel.h" + +// Boilerplate, copies properties to new state +void ARWTHVRPlayerState::CopyProperties(class APlayerState* PlayerState) +{ + Super::CopyProperties(PlayerState); + + if (IsValid(PlayerState)) + { + ARWTHVRPlayerState* RWTHVRPlayerState = Cast<ARWTHVRPlayerState>(PlayerState); + if (IsValid(RWTHVRPlayerState)) + { + RWTHVRPlayerState->SetPlayerType(GetPlayerType()); + } + } +} + +// Boilerplate +void ARWTHVRPlayerState::OverrideWith(class APlayerState* PlayerState) +{ + Super::OverrideWith(PlayerState); + + if (IsValid(PlayerState)) + { + const ARWTHVRPlayerState* RWTHVRPlayerState = Cast<ARWTHVRPlayerState>(PlayerState); + if (IsValid(RWTHVRPlayerState)) + { + SetPlayerType(RWTHVRPlayerState->GetPlayerType()); + } + } +} + +// Replicate our property similar to the other state properties +void ARWTHVRPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + FDoRepLifetimeParams SharedParams; + SharedParams.bIsPushBased = true; + + DOREPLIFETIME_WITH_PARAMS_FAST(ARWTHVRPlayerState, PlayerType, SharedParams); +} + +void ARWTHVRPlayerState::SetPlayerTypeServerRpc_Implementation(const EPlayerType NewPlayerType) +{ + SetPlayerType(NewPlayerType); +} + +void ARWTHVRPlayerState::SetPlayerType(const EPlayerType NewPlayerType) +{ + MARK_PROPERTY_DIRTY_FROM_NAME(ARWTHVRPlayerState, PlayerType, this); + PlayerType = NewPlayerType; +} + +void ARWTHVRPlayerState::RequestSetPlayerType(const EPlayerType NewPlayerType) +{ + if (HasAuthority()) + { + SetPlayerType(NewPlayerType); + } + else + { + SetPlayerTypeServerRpc(NewPlayerType); + } +} diff --git a/Source/RWTHVRToolkit/Private/Core/VRTransformRep.h b/Source/RWTHVRToolkit/Private/Core/VRTransformRep.h new file mode 100644 index 0000000000000000000000000000000000000000..c9bc577bc904b70b878ef9c14f47f3fd759b6b03 --- /dev/null +++ b/Source/RWTHVRToolkit/Private/Core/VRTransformRep.h @@ -0,0 +1,43 @@ +#pragma once + +#include "CoreMinimal.h" +#include "VRTransformRep.generated.h" + +// Simple custom transform struct for more efficient repliation, from VRE Plugin +USTRUCT() +struct RWTHVRTOOLKIT_API FVRTransformRep +{ + GENERATED_USTRUCT_BODY() + +public: + UPROPERTY(Transient) + FVector Position; + + UPROPERTY(Transient) + FRotator Rotation; + + FVRTransformRep() + { + Position = FVector::ZeroVector; + Rotation = FRotator::ZeroRotator; + } + + /** + * @param Ar FArchive to read or write from. + * @param Map PackageMap used to resolve references to UObject* + * @param bOutSuccess return value to signify if the serialization was succesfull (if false, an error will be logged by the calling function) + * + * @return return true if the serialization was fully mapped. If false, the property will be considered 'dirty' and will replicate again on the next update. + * This is needed for UActor* properties. If an actor's Actorchannel is not fully mapped, properties referencing it must stay dirty. + * Note that UPackageMap::SerializeObject returns false if an object is unmapped. Generally, you will want to return false from your ::NetSerialize + * if you make any calls to ::SerializeObject that return false. + * + */ + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + bOutSuccess = true; + bOutSuccess &= SerializePackedVector<1, 24>(Position, Ar); + Rotation.SerializeCompressed(Ar); + return bOutSuccess; + } +}; \ No newline at end of file diff --git a/Source/RWTHVRToolkit/Private/Interaction/GrabComponent.cpp b/Source/RWTHVRToolkit/Private/Interaction/GrabComponent.cpp index 57a0b05f0996be79561bbbbed5ac0e665b5cbdd1..db49c8cb60a7279ab55bf66f55241c91b9b83b06 100644 --- a/Source/RWTHVRToolkit/Private/Interaction/GrabComponent.cpp +++ b/Source/RWTHVRToolkit/Private/Interaction/GrabComponent.cpp @@ -93,11 +93,19 @@ void UGrabComponent::BeginPlay() void UGrabComponent::SetupInputActions() { - auto InputSubsystem = UVirtualRealityUtilities::GetVRPawnLocalPlayerSubsystem(GetWorld()); + const APawn* Pawn = Cast<APawn>(GetOwner()); + const APlayerController* PlayerController = Cast<APlayerController>(Pawn->GetController()); + const ULocalPlayer* LP = PlayerController ? PlayerController->GetLocalPlayer() : nullptr; + if (LP == nullptr) + return; + + UEnhancedInputLocalPlayerSubsystem* InputSubsystem = LP->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(); // add Input Mapping context InputSubsystem->AddMappingContext(IMCGrab,0); - UEnhancedInputComponent* EI = UVirtualRealityUtilities::GetVRPawnInputComponent(GetWorld()); + UEnhancedInputComponent* EI = Cast<UEnhancedInputComponent>(Pawn->InputComponent); + if (EI == nullptr) + return; EI->BindAction(GrabInputAction, ETriggerEvent::Started, this, &UGrabComponent::OnBeginGrab); EI->BindAction(GrabInputAction, ETriggerEvent::Completed, this, &UGrabComponent::OnEndGrab); diff --git a/Source/RWTHVRToolkit/Private/Interaction/RaycastSelectionComponent.cpp b/Source/RWTHVRToolkit/Private/Interaction/RaycastSelectionComponent.cpp index 1ac8cf0e187f57a2c8418c732ba883078d1aed27..0ac53f6aa1d28852d1eff129512f87684cd9fae1 100644 --- a/Source/RWTHVRToolkit/Private/Interaction/RaycastSelectionComponent.cpp +++ b/Source/RWTHVRToolkit/Private/Interaction/RaycastSelectionComponent.cpp @@ -79,11 +79,19 @@ void URaycastSelectionComponent::TickComponent(float DeltaTime, ELevelTick TickT void URaycastSelectionComponent::SetupInputActions() { - auto InputSubsystem = UVirtualRealityUtilities::GetVRPawnLocalPlayerSubsystem(GetWorld()); + const APawn* Pawn = Cast<APawn>(GetOwner()); + const APlayerController* PlayerController = Cast<APlayerController>(Pawn->GetController()); + const ULocalPlayer* LP = PlayerController ? PlayerController->GetLocalPlayer() : nullptr; + if (LP == nullptr) + return; + UEnhancedInputLocalPlayerSubsystem* InputSubsystem = LP->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(); + // add Input Mapping context InputSubsystem->AddMappingContext(IMCRaycastSelection,0); - UEnhancedInputComponent* EI = UVirtualRealityUtilities::GetVRPawnInputComponent(GetWorld()); + UEnhancedInputComponent* EI = Cast<UEnhancedInputComponent>(Pawn->InputComponent); + if (EI == nullptr) + return; EI->BindAction(RayCastSelectInputAction, ETriggerEvent::Started, this, &URaycastSelectionComponent::OnBeginSelect); EI->BindAction(RayCastSelectInputAction, ETriggerEvent::Completed, this, &URaycastSelectionComponent::OnEndSelect); diff --git a/Source/RWTHVRToolkit/Private/Pawn/BasicVRInteractionComponent.cpp b/Source/RWTHVRToolkit/Private/Pawn/BasicVRInteractionComponent.cpp index 0a2c13ba1e50dfc2bfc670af9e436e7ea5a043d9..f3e931cc9820e0b4bf99335367706cce43fbee01 100644 --- a/Source/RWTHVRToolkit/Private/Pawn/BasicVRInteractionComponent.cpp +++ b/Source/RWTHVRToolkit/Private/Pawn/BasicVRInteractionComponent.cpp @@ -3,15 +3,12 @@ #include "Pawn/BasicVRInteractionComponent.h" - #include "Interaction/Clickable.h" #include "Interaction/Grabable.h" #include "Interaction/Targetable.h" #include "Interaction/GrabbingBehaviorComponent.h" #include "Misc/Optional.h" #include "DrawDebugHelpers.h" -#include "Components/WidgetComponent.h" -#include "UObject/ConstructorHelpers.h" DEFINE_LOG_CATEGORY(LogVRInteractionComponent); @@ -24,33 +21,32 @@ UBasicVRInteractionComponent::UBasicVRInteractionComponent() // Setup the interaction ray. InteractionRay = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Interaction Ray")); - //this ray model has an inlayed cross with flipped normals so it can be seen as a cross in desktop mode where the right hand is attached to the head - ConstructorHelpers::FObjectFinder<UStaticMesh> MeshAsset(TEXT("/RWTHVRToolkit/PointingRay/Ray_Mesh")); - if (MeshAsset.Object != nullptr) - { - InteractionRay->SetStaticMesh(MeshAsset.Object); - } + InteractionRay->SetCastShadow(false); // turns off collisions as the InteractionRay is only meant to visualize the ray InteractionRay->SetCollisionProfileName(TEXT("NoCollision")); + // The static mesh reference is set in the blueprint, avoid ConstructorHelpers and hardcoded paths! + // this ray model has an inlayed cross with flipped normals so it can be seen as a cross in desktop mode where the right hand is attached to the head + bShowDebug = false; //otherwise the WidgetInteractionComponent debug vis is shown - InteractionSource = EWidgetInteractionSource::Custom; //can also be kept at default (World), this way, however, we efficiently reuse the line traces - + InteractionSource = EWidgetInteractionSource::Custom; + //can also be kept at default (World), this way, however, we efficiently reuse the line traces } void UBasicVRInteractionComponent::BeginPlay() { Super::BeginPlay(); - + //WidgetInteractionComponent InteractionDistance = MaxClickDistance; - InteractionRay->SetRelativeScale3D(FVector(MaxClickDistance / 100.0f, 0.5f, 0.5f)); //the ray model has a length of 100cm (and is a bit too big in Y/Z dir) + InteractionRay->SetRelativeScale3D(FVector(MaxClickDistance / 100.0f, 0.5f, 0.5f)); + //the ray model has a length of 100cm (and is a bit too big in Y/Z dir) SetInteractionRayVisibility(InteractionRayVisibility); } void UBasicVRInteractionComponent::BeginInteraction() { - if(!InteractionRayEmitter) return; - + if (!InteractionRayEmitter) return; + // start and end point for raytracing const FTwoVectors StartEnd = GetHandRay(MaxClickDistance); TOptional<FHitResult> Hit = RaytraceForFirstHit(StartEnd); @@ -63,16 +59,16 @@ void UBasicVRInteractionComponent::BeginInteraction() SetCustomHitResult(Hit.GetValue()); //if !bCanRaytraceEveryTick, you have to click twice, since the first tick it only highlights and can't directly click PressPointerKey(EKeys::LeftMouseButton); - - + + if (HitActor && HitActor->Implements<UGrabable>() && Hit->Distance < MaxGrabDistance) { // call grabable actors function so he reacts to our grab IGrabable::Execute_OnBeginGrab(HitActor); - + // save it for later, is needed every tick Behavior = HitActor->FindComponentByClass<UGrabbingBehaviorComponent>(); - if ( Behavior == nullptr) + if (Behavior == nullptr) HandlePhysicsAndAttachActor(HitActor); // we save the grabbedActor in a general form to access all of AActors functions easily later @@ -86,11 +82,11 @@ void UBasicVRInteractionComponent::BeginInteraction() void UBasicVRInteractionComponent::EndInteraction() { - if(!InteractionRayEmitter) return; + if (!InteractionRayEmitter) return; //end interaction of WidgetInteractionComponent ReleasePointerKey(EKeys::LeftMouseButton); - + // if we didnt grab anyone there is no need to release if (GrabbedActor == nullptr) return; @@ -101,11 +97,13 @@ void UBasicVRInteractionComponent::EndInteraction() // Detach the Actor if (GrabbedActor->FindComponentByClass<UGrabbingBehaviorComponent>() == nullptr) { - if (ComponentSimulatingPhysics) { + if (ComponentSimulatingPhysics) + { ComponentSimulatingPhysics->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); ComponentSimulatingPhysics->SetSimulatePhysics(true); } - else { + else + { GrabbedActor->GetRootComponent()->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); } } @@ -117,15 +115,16 @@ void UBasicVRInteractionComponent::EndInteraction() } // Called every frame -void UBasicVRInteractionComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +void UBasicVRInteractionComponent::TickComponent(float DeltaTime, ELevelTick TickType, + FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); if (!InteractionRayEmitter) return; - + // if our Grabable Actor is not constrained we need to calculate the position dynamically if (Behavior != nullptr) - { + { // specifies the hand in space const FVector HandPos = InteractionRayEmitter->GetComponentLocation(); const FQuat HandQuat = InteractionRayEmitter->GetComponentQuat(); @@ -142,7 +141,7 @@ void UBasicVRInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tic if (!Hit.IsSet() || !Hit->GetActor()) { - if(InteractionRayVisibility==EInteractionRayVisibility::VisibleOnHoverOnly) + if (InteractionRayVisibility == EInteractionRayVisibility::VisibleOnHoverOnly) { InteractionRay->SetVisibility(false); } @@ -150,13 +149,13 @@ void UBasicVRInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tic // Execute leave event on the actor that lost the focus if there was one if (LastActorHit && LastActorHit->Implements<UTargetable>()) { - ITargetable::Execute_OnTargetedLeave(LastActorHit); + ITargetable::Execute_OnTargetedLeave(LastActorHit); } LastActorHit = nullptr; return; } - + AActor* HitActor = Hit->GetActor(); // Execute Leave and enter events when the focused actor changed @@ -183,9 +182,9 @@ void UBasicVRInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tic // widget interaction SetCustomHitResult(Hit.GetValue()); - if(InteractionRayVisibility==EInteractionRayVisibility::VisibleOnHoverOnly) + if (InteractionRayVisibility == EInteractionRayVisibility::VisibleOnHoverOnly) { - if(HitActor->Implements<UTargetable>() || HitActor->Implements<UClickable>() || IsOverInteractableWidget()) + if (HitActor->Implements<UTargetable>() || HitActor->Implements<UClickable>() || IsOverInteractableWidget()) { InteractionRay->SetVisibility(true); } @@ -199,7 +198,7 @@ void UBasicVRInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tic void UBasicVRInteractionComponent::Initialize(USceneComponent* RayEmitter) { - if(InteractionRayEmitter) return; /* Return if already initialized */ + if (InteractionRayEmitter) return; /* Return if already initialized */ InteractionRayEmitter = RayEmitter; @@ -210,7 +209,7 @@ void UBasicVRInteractionComponent::Initialize(USceneComponent* RayEmitter) void UBasicVRInteractionComponent::SetInteractionRayVisibility(EInteractionRayVisibility NewVisibility) { InteractionRayVisibility = NewVisibility; - if(InteractionRay) + if (InteractionRay) { switch (InteractionRayVisibility) { @@ -221,31 +220,38 @@ void UBasicVRInteractionComponent::SetInteractionRayVisibility(EInteractionRayVi case Invisible: InteractionRay->SetVisibility(false); break; + default: ; } } - if(InteractionRayVisibility==EInteractionRayVisibility::VisibleOnHoverOnly && !bCanRaytraceEveryTick) + if (InteractionRayVisibility == EInteractionRayVisibility::VisibleOnHoverOnly && !bCanRaytraceEveryTick) { - UE_LOG(LogVRInteractionComponent, Warning, TEXT("VisibleOnHoverOnly needs bCanRaytraceEveryTick=true, so this is set!")); - bCanRaytraceEveryTick=true; + UE_LOG(LogVRInteractionComponent, Warning, + TEXT("VisibleOnHoverOnly needs bCanRaytraceEveryTick=true, so this is set!")); + bCanRaytraceEveryTick = true; } - if(InteractionRayVisibility==EInteractionRayVisibility::Visible && !bCanRaytraceEveryTick) + if (InteractionRayVisibility == EInteractionRayVisibility::Visible && !bCanRaytraceEveryTick) { - UE_LOG(LogVRInteractionComponent, Warning, TEXT("VisibleOnHoverOnly will need two clicks to interact with widgets if bCanRaytraceEveryTick is not set!")); + UE_LOG(LogVRInteractionComponent, Warning, + TEXT( + "VisibleOnHoverOnly will need two clicks to interact with widgets if bCanRaytraceEveryTick is not set!" + )); } } -void UBasicVRInteractionComponent::HandlePhysicsAndAttachActor(AActor* HitActor) +void UBasicVRInteractionComponent::HandlePhysicsAndAttachActor(const AActor* HitActor) { UPrimitiveComponent* PhysicsSimulatingComp = GetFirstComponentSimulatingPhysics(HitActor); const FAttachmentTransformRules Rules = FAttachmentTransformRules(EAttachmentRule::KeepWorld, false); - - if (PhysicsSimulatingComp) { + + if (PhysicsSimulatingComp) + { PhysicsSimulatingComp->SetSimulatePhysics(false); - PhysicsSimulatingComp->AttachToComponent(InteractionRayEmitter, Rules); + PhysicsSimulatingComp->AttachToComponent(InteractionRayEmitter, Rules); ComponentSimulatingPhysics = PhysicsSimulatingComp; } - else { + else + { HitActor->GetRootComponent()->AttachToComponent(InteractionRayEmitter, Rules); } } @@ -262,40 +268,44 @@ FTwoVectors UBasicVRInteractionComponent::GetHandRay(const float Length) const TOptional<FHitResult> UBasicVRInteractionComponent::RaytraceForFirstHit(const FTwoVectors& Ray) const { const FVector Start = Ray.v1; - const FVector End = Ray.v2; - + const FVector End = Ray.v2; + // will be filled by the Line Trace Function FHitResult Hit; - FCollisionQueryParams Params; + FCollisionQueryParams Params; Params.AddIgnoredActor(GetOwner()->GetUniqueID()); // prevents actor hitting itself - if (GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECollisionChannel::ECC_Visibility,Params)) + if (GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECollisionChannel::ECC_Visibility, Params)) return {Hit}; else return {}; } -UPrimitiveComponent* GetFirstComponentSimulatingPhysics(const AActor* TargetActor) +UPrimitiveComponent* UBasicVRInteractionComponent::GetFirstComponentSimulatingPhysics(const AActor* TargetActor) { TArray<UPrimitiveComponent*> PrimitiveComponents; - TargetActor->GetComponents<UPrimitiveComponent>(PrimitiveComponents); + TargetActor->GetComponents<UPrimitiveComponent>(PrimitiveComponents); // find any component that simulates physics, then traverse the hierarchy - for (const auto& Component : PrimitiveComponents) { - if (Component->IsSimulatingPhysics()) { + for (const auto& Component : PrimitiveComponents) + { + if (Component->IsSimulatingPhysics()) + { return GetHighestParentSimulatingPhysics(Component); - } + } } return nullptr; } // recursively goes up the hierarchy and returns the highest parent simulating physics -UPrimitiveComponent* GetHighestParentSimulatingPhysics(UPrimitiveComponent* Comp) -{ - if (Cast<UPrimitiveComponent>(Comp->GetAttachParent()) && Comp->GetAttachParent()->IsSimulatingPhysics()) { +UPrimitiveComponent* UBasicVRInteractionComponent::GetHighestParentSimulatingPhysics(UPrimitiveComponent* Comp) +{ + if (Cast<UPrimitiveComponent>(Comp->GetAttachParent()) && Comp->GetAttachParent()->IsSimulatingPhysics()) + { return GetHighestParentSimulatingPhysics(Cast<UPrimitiveComponent>(Comp->GetAttachParent())); } - else { + else + { return Comp; } } diff --git a/Source/RWTHVRToolkit/Private/Pawn/ContinuousMovementComponent.cpp b/Source/RWTHVRToolkit/Private/Pawn/ContinuousMovementComponent.cpp index 5260c0d5dc344a85734346a9fef7ac7fdda0db63..ff3d0e11a9de8653c1ca26d09cf093a025b310ec 100644 --- a/Source/RWTHVRToolkit/Private/Pawn/ContinuousMovementComponent.cpp +++ b/Source/RWTHVRToolkit/Private/Pawn/ContinuousMovementComponent.cpp @@ -10,49 +10,39 @@ #include "Utility/VirtualRealityUtilities.h" #include "MotionControllerComponent.h" -void UContinuousMovementComponent::BeginPlay() +void UContinuousMovementComponent::SetupPlayerInput(UInputComponent* PlayerInputComponent) { - Super::BeginPlay(); - - SetupInputActions(); -} + Super::SetupPlayerInput(PlayerInputComponent); + if (!VRPawn || !VRPawn->HasLocalNetOwner() || !InputSubsystem) + { + return; + } -void UContinuousMovementComponent::SetupInputActions() -{ - Super::SetupInputActions(); - - const AVirtualRealityPawn* VRPawn = Cast<AVirtualRealityPawn>(GetOwner()); // simple way of changing the handedness - if(bMoveWithRightHand) + if (bMoveWithRightHand) { MovementHand = VRPawn->RightHand; RotationHand = VRPawn->LeftHand; IMCMovement = IMCMovementRight; - } else + } + else { MovementHand = VRPawn->LeftHand; RotationHand = VRPawn->RightHand; IMCMovement = IMCMovementLeft; } - const APlayerController* PlayerController = Cast<APlayerController>(VRPawn->GetController()); - UEnhancedInputLocalPlayerSubsystem* InputSubsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()); - if(!InputSubsystem) - { - UE_LOG(Toolkit,Error,TEXT("InputSubsystem IS NOT VALID")); - return; - } // add Input Mapping context - InputSubsystem->AddMappingContext(IMCMovement,0); - - UEnhancedInputComponent* EI = Cast<UEnhancedInputComponent>(VRPawn->InputComponent); - if(!EI) + InputSubsystem->AddMappingContext(IMCMovement, 0); + + UEnhancedInputComponent* EI = Cast<UEnhancedInputComponent>(PlayerInputComponent); + if (!EI) { - UE_LOG(Toolkit,Error,TEXT("Cannot cast Input Component to Enhanced Inpu Component in VRPawnMovement")); + UE_LOG(Toolkit, Error, TEXT("Cannot cast Input Component to Enhanced Inpu Component in VRPawnMovement")); return; } - + // continuous steering EI->BindAction(Move, ETriggerEvent::Triggered, this, &UContinuousMovementComponent::OnMove); EI->BindAction(MoveUp, ETriggerEvent::Triggered, this, &UContinuousMovementComponent::OnMoveUp); @@ -62,34 +52,40 @@ void UContinuousMovementComponent::SetupInputActions() void UContinuousMovementComponent::OnMove(const FInputActionValue& Value) { - AVirtualRealityPawn* VRPawn = Cast<AVirtualRealityPawn>(GetOwner()); - const bool bGazeDirected = UVirtualRealityUtilities::IsDesktopMode() || SteeringMode == EVRSteeringModes::STEER_GAZE_DIRECTED; - - const FVector ForwardDir = bGazeDirected ? VRPawn->HeadCameraComponent->GetForwardVector() : MovementHand->GetForwardVector(); - const FVector RightDir = bGazeDirected ? VRPawn->HeadCameraComponent->GetRightVector() : MovementHand->GetRightVector(); - - if (VRPawn->Controller != nullptr) + if (!VRPawn || !VRPawn->Controller) + return; + + const bool bGazeDirected = UVirtualRealityUtilities::IsDesktopMode() || SteeringMode == + EVRSteeringModes::STEER_GAZE_DIRECTED; + + const FVector ForwardDir = bGazeDirected + ? VRPawn->HeadCameraComponent->GetForwardVector() + : MovementHand->GetForwardVector(); + const FVector RightDir = bGazeDirected + ? VRPawn->HeadCameraComponent->GetRightVector() + : MovementHand->GetRightVector(); + + const FVector2D MoveValue = Value.Get<FVector2D>(); + + // Forward/Backward direction + if (MoveValue.X != 0.f) + { + VRPawn->AddMovementInput(ForwardDir, MoveValue.X); + } + + // Right/Left direction + if (MoveValue.Y != 0.f) { - const FVector2D MoveValue = Value.Get<FVector2D>(); - - // Forward/Backward direction - if (MoveValue.X != 0.f) - { - VRPawn->AddMovementInput(ForwardDir, MoveValue.X); - } - - // Right/Left direction - if (MoveValue.Y != 0.f) - { - VRPawn->AddMovementInput(RightDir, MoveValue.Y); - } + VRPawn->AddMovementInput(RightDir, MoveValue.Y); } } void UContinuousMovementComponent::OnMoveUp(const FInputActionValue& Value) { - AVirtualRealityPawn* VRPawn = Cast<AVirtualRealityPawn>(GetOwner()); - const float MoveValue = Value.Get<FVector2D>().X; + if (!VRPawn) + return; + + const float MoveValue = Value.Get<FVector2D>().X; //the right hand is rotated on desktop to follow the cursor so it's forward is also changing with cursor position VRPawn->AddMovementInput(FVector::UpVector, MoveValue); } diff --git a/Source/RWTHVRToolkit/Private/Pawn/InputExtensionInterface.cpp b/Source/RWTHVRToolkit/Private/Pawn/InputExtensionInterface.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8c429dc9d4fc49f41ecac59d03754eb52235e417 --- /dev/null +++ b/Source/RWTHVRToolkit/Private/Pawn/InputExtensionInterface.cpp @@ -0,0 +1,12 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Pawn/InputExtensionInterface.h" + +UEnhancedInputLocalPlayerSubsystem* IInputExtensionInterface::GetEnhancedInputLocalPlayerSubsystem(APawn* Pawn) const +{ + const APlayerController* PlayerController = Pawn ? Cast<APlayerController>(Pawn->GetController()) : nullptr; + const ULocalPlayer* LP = PlayerController ? PlayerController->GetLocalPlayer() : nullptr; + + return LP->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(); +} diff --git a/Source/RWTHVRToolkit/Private/Pawn/MovementComponentBase.cpp b/Source/RWTHVRToolkit/Private/Pawn/MovementComponentBase.cpp index e0fe2d6d4bf545f34b79e43cfa10ea5fea5cc9ad..b4f680c8bec3453d84537af95927ecd8381b48b4 100644 --- a/Source/RWTHVRToolkit/Private/Pawn/MovementComponentBase.cpp +++ b/Source/RWTHVRToolkit/Private/Pawn/MovementComponentBase.cpp @@ -11,49 +11,48 @@ #include "Pawn/VRPawnInputConfig.h" #include "Utility/VirtualRealityUtilities.h" -void UMovementComponentBase::BeginPlay() +void UMovementComponentBase::SetupPlayerInput(UInputComponent* PlayerInputComponent) { - Super::BeginPlay(); + IInputExtensionInterface::SetupPlayerInput(PlayerInputComponent); - SetupInputActions(); -} + VRPawn = Cast<AVirtualRealityPawn>(GetOwner()); -void UMovementComponentBase::SetupInputActions() -{ + if (!VRPawn || !VRPawn->HasLocalNetOwner()) + { + return; + } - const AVirtualRealityPawn* VRPawn = Cast<AVirtualRealityPawn>(GetOwner()); - - const APlayerController* PlayerController = Cast<APlayerController>(VRPawn->GetController()); - UEnhancedInputLocalPlayerSubsystem* InputSubsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()); - if(!InputSubsystem) + InputSubsystem = GetEnhancedInputLocalPlayerSubsystem(VRPawn); + if (!InputSubsystem) { - UE_LOG(Toolkit,Error,TEXT("InputSubsystem IS NOT VALID")); + UE_LOG(Toolkit, Error, TEXT("InputSubsystem IS NOT VALID")); return; } - // add Input Mapping context - InputSubsystem->AddMappingContext(IMCRotation,0); - UEnhancedInputComponent* EI = Cast<UEnhancedInputComponent>(VRPawn->InputComponent); - if(!EI) + // add Input Mapping context + InputSubsystem->AddMappingContext(IMCRotation, 0); + + UEnhancedInputComponent* EI = Cast<UEnhancedInputComponent>(PlayerInputComponent); + if (!EI) { - UE_LOG(Toolkit,Error,TEXT("Cannot cast Input Component to Enhanced Inpu Component in VRPawnMovement")); + UE_LOG(Toolkit, Error, TEXT("Cannot cast Input Component to Enhanced Inpu Component in VRPawnMovement")); return; } - // turning - if(bAllowTurning) + if (bAllowTurning) { // no snap turning for desktop mode - if(bSnapTurn && !UVirtualRealityUtilities::IsDesktopMode()) + if (bSnapTurn && !UVirtualRealityUtilities::IsDesktopMode()) { EI->BindAction(Turn, ETriggerEvent::Started, this, &UMovementComponentBase::OnBeginSnapTurn); - } else + } + else { EI->BindAction(Turn, ETriggerEvent::Triggered, this, &UMovementComponentBase::OnBeginTurn); } } - + // bind additional functions for desktop rotations if (UVirtualRealityUtilities::IsDesktopMode()) { @@ -72,40 +71,41 @@ void UMovementComponentBase::EndDesktopRotation() bApplyDesktopRotation = false; } - void UMovementComponentBase::OnBeginTurn(const FInputActionValue& Value) { - AVirtualRealityPawn* VRPawn = Cast<AVirtualRealityPawn>(GetOwner()); + if (UVirtualRealityUtilities::IsDesktopMode() && !bApplyDesktopRotation) + return; - if(UVirtualRealityUtilities::IsDesktopMode() && !bApplyDesktopRotation) return; + if (!VRPawn || !VRPawn->Controller) + return; + + const FVector2D TurnValue = Value.Get<FVector2D>(); - if (VRPawn->Controller != nullptr) + if (TurnValue.X != 0.f) { - const FVector2D TurnValue = Value.Get<FVector2D>(); - - if (TurnValue.X != 0.f) - { - VRPawn->AddControllerYawInput(TurnRateFactor * TurnValue.X); - } - - if (TurnValue.Y != 0.f) + VRPawn->AddControllerYawInput(TurnRateFactor * TurnValue.X); + } + + if (TurnValue.Y != 0.f) + { + if (UVirtualRealityUtilities::IsDesktopMode() && bApplyDesktopRotation) { - if (UVirtualRealityUtilities::IsDesktopMode() && bApplyDesktopRotation) - { - VRPawn->AddControllerPitchInput(TurnRateFactor * -TurnValue.Y); - } + VRPawn->AddControllerPitchInput(TurnRateFactor * -TurnValue.Y); } } } void UMovementComponentBase::OnBeginSnapTurn(const FInputActionValue& Value) { - AVirtualRealityPawn* VRPawn = Cast<AVirtualRealityPawn>(GetOwner()); + if (!VRPawn || !VRPawn->Controller) + return; + const FVector2D TurnValue = Value.Get<FVector2D>(); if (TurnValue.X > 0.f) { VRPawn->AddControllerYawInput(SnapTurnAngle); - } else if (TurnValue.X < 0.f) + } + else if (TurnValue.X < 0.f) { VRPawn->AddControllerYawInput(-SnapTurnAngle); } diff --git a/Source/RWTHVRToolkit/Private/Pawn/MovementComponentBase.h b/Source/RWTHVRToolkit/Private/Pawn/MovementComponentBase.h index 0cc2d2a297fc1f3ba2ba92aadbe5130a2054317d..0a9d869e90f6a8ba0ac61c109e0a1475b084023b 100644 --- a/Source/RWTHVRToolkit/Private/Pawn/MovementComponentBase.h +++ b/Source/RWTHVRToolkit/Private/Pawn/MovementComponentBase.h @@ -4,19 +4,20 @@ #include "CoreMinimal.h" #include "Components/ActorComponent.h" +#include "Pawn/InputExtensionInterface.h" #include "MovementComponentBase.generated.h" /** * */ UCLASS(Blueprintable) -class RWTHVRTOOLKIT_API UMovementComponentBase : public UActorComponent +class RWTHVRTOOLKIT_API UMovementComponentBase : public UActorComponent, public IInputExtensionInterface { GENERATED_BODY() public: - - virtual void BeginPlay() override; + + virtual void SetupPlayerInput(UInputComponent* PlayerInputComponent) override; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VR Movement") bool bAllowTurning = true; @@ -53,14 +54,15 @@ public: void EndDesktopRotation(); protected: - void SetupInputActions(); UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VR Movement|Input") class UInputMappingContext* IMCRotation; -private: + UPROPERTY() + AVirtualRealityPawn* VRPawn; + UPROPERTY() + UEnhancedInputLocalPlayerSubsystem* InputSubsystem; bool bApplyDesktopRotation = false; - }; diff --git a/Source/RWTHVRToolkit/Private/Pawn/ReplicatedCameraComponent.cpp b/Source/RWTHVRToolkit/Private/Pawn/ReplicatedCameraComponent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1c8a8563d5eab7560adb5b9b95f7f70867ff29b3 --- /dev/null +++ b/Source/RWTHVRToolkit/Private/Pawn/ReplicatedCameraComponent.cpp @@ -0,0 +1,82 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Pawn/ReplicatedCameraComponent.h" + +#include "Net/UnrealNetwork.h" + +UReplicatedCameraComponent::UReplicatedCameraComponent() +{ + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.SetTickFunctionEnable(true); + SetIsReplicatedByDefault(true); + + // Direct transform replication + ControllerNetUpdateRate = 100.0f; // 100 htz is default + ControllerNetUpdateCount = 0.0f; +} + +// Naive direct transform replication (replace with input rep?) + +void UReplicatedCameraComponent::UpdateState(float DeltaTime) +{ + if (GetOwner()->HasLocalNetOwner()) + { + if (GetIsReplicated()) + { + const FVector Loc = GetRelativeLocation(); + const FRotator Rot = GetRelativeRotation(); + + if (!Loc.Equals(ReplicatedTransform.Position) || !Rot.Equals(ReplicatedTransform.Rotation)) + { + ControllerNetUpdateCount += DeltaTime; + if (ControllerNetUpdateCount >= (1.0f / ControllerNetUpdateRate)) // todo save inverse? + { + ControllerNetUpdateCount = 0.0f; + + ReplicatedTransform.Position = Loc; + ReplicatedTransform.Rotation = Rot; + if (GetNetMode() == NM_Client) // why do we differentiate here between netmode and authority? + { + ServerSendControllerTransformRpc(ReplicatedTransform); + } + } + } + } + } +} + +void UReplicatedCameraComponent::TickComponent(float DeltaTime, ELevelTick TickType, + FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + UpdateState(DeltaTime); +} + +void UReplicatedCameraComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty >& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeLocation); + DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeRotation); + DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D); + + // Skipping the owner with this as the owner will use the controllers location directly + DOREPLIFETIME_CONDITION(UReplicatedCameraComponent, ReplicatedTransform, COND_SkipOwner); + DOREPLIFETIME(UReplicatedCameraComponent, ControllerNetUpdateRate); +} + +void UReplicatedCameraComponent::ServerSendControllerTransformRpc_Implementation(FVRTransformRep NewTransform) +{ + // Store new transform and trigger OnRep_Function + ReplicatedTransform = NewTransform; + + if (!GetOwner()->HasLocalNetOwner()) + OnRep_ReplicatedTransform(); +} + +bool UReplicatedCameraComponent::ServerSendControllerTransformRpc_Validate(FVRTransformRep NewTransform) +{ + return true; + // Optionally check to make sure that player is inside of their bounds and deny it if they aren't? +} \ No newline at end of file diff --git a/Source/RWTHVRToolkit/Private/Pawn/ReplicatedMotionControllerComponent.cpp b/Source/RWTHVRToolkit/Private/Pawn/ReplicatedMotionControllerComponent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4c8c06813f90c420dc5d1dee078520a5e6a8d6a2 --- /dev/null +++ b/Source/RWTHVRToolkit/Private/Pawn/ReplicatedMotionControllerComponent.cpp @@ -0,0 +1,83 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Pawn/ReplicatedMotionControllerComponent.h" + + +#include "Net/UnrealNetwork.h" + +UReplicatedMotionControllerComponent::UReplicatedMotionControllerComponent() +{ + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.SetTickFunctionEnable(true); + SetIsReplicatedByDefault(true); + + // Direct transform replication + ControllerNetUpdateRate = 100.0f; // 100 htz is default + ControllerNetUpdateCount = 0.0f; +} + +// Naive direct transform replication (replace with input rep?) + +void UReplicatedMotionControllerComponent::UpdateState(float DeltaTime) +{ + if (GetOwner()->HasLocalNetOwner()) + { + if (GetIsReplicated()) + { + const FVector Loc = GetRelativeLocation(); + const FRotator Rot = GetRelativeRotation(); + + if (!Loc.Equals(ReplicatedTransform.Position) || !Rot.Equals(ReplicatedTransform.Rotation)) + { + ControllerNetUpdateCount += DeltaTime; + if (ControllerNetUpdateCount >= (1.0f / ControllerNetUpdateRate)) // todo save inverse? + { + ControllerNetUpdateCount = 0.0f; + + ReplicatedTransform.Position = Loc; + ReplicatedTransform.Rotation = Rot; + if (GetNetMode() == NM_Client) // why do we differentiate here between netmode and authority? + { + SendControllerTransform_ServerRpc(ReplicatedTransform); + } + } + } + } + } +} + +void UReplicatedMotionControllerComponent::TickComponent(float DeltaTime, ELevelTick TickType, + FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + UpdateState(DeltaTime); +} + +void UReplicatedMotionControllerComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty >& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeLocation); + DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeRotation); + DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D); + + // Skipping the owner with this as the owner will use the controllers location directly + DOREPLIFETIME_CONDITION(UReplicatedMotionControllerComponent, ReplicatedTransform, COND_SkipOwner); + DOREPLIFETIME(UReplicatedMotionControllerComponent, ControllerNetUpdateRate); +} + +void UReplicatedMotionControllerComponent::SendControllerTransform_ServerRpc_Implementation(FVRTransformRep NewTransform) +{ + // Store new transform and trigger OnRep_Function + ReplicatedTransform = NewTransform; + + if (!GetOwner()->HasLocalNetOwner()) + OnRep_ReplicatedTransform(); +} + +bool UReplicatedMotionControllerComponent::SendControllerTransform_ServerRpc_Validate(FVRTransformRep NewTransform) +{ + return true; + // Optionally check to make sure that player is inside of their bounds and deny it if they aren't? +} \ No newline at end of file diff --git a/Source/RWTHVRToolkit/Private/Pawn/TeleportationComponent.cpp b/Source/RWTHVRToolkit/Private/Pawn/TeleportationComponent.cpp index 3ec3aee1c04ebc943a2352dfb1c872f3f23f4c10..00864280dd13027fb3556e69eccdc4d1b9107993 100644 --- a/Source/RWTHVRToolkit/Private/Pawn/TeleportationComponent.cpp +++ b/Source/RWTHVRToolkit/Private/Pawn/TeleportationComponent.cpp @@ -16,18 +16,20 @@ #include "MotionControllerComponent.h" -// Called when the game starts -void UTeleportationComponent::BeginPlay() +void UTeleportationComponent::SetupPlayerInput(UInputComponent* PlayerInputComponent) { - Super::BeginPlay(); - - SetupInputActions(); + Super::SetupPlayerInput(PlayerInputComponent); + + if (!VRPawn || !VRPawn->HasLocalNetOwner() || !InputSubsystem) + { + return; + } TeleportTraceComponent = UNiagaraFunctionLibrary::SpawnSystemAtLocation ( GetWorld(), TeleportTraceSystem, - GetOwner()->GetActorLocation(), + VRPawn->GetActorLocation(), FRotator(0), FVector(1), true, @@ -38,48 +40,34 @@ void UTeleportationComponent::BeginPlay() FActorSpawnParameters SpawnParameters = FActorSpawnParameters(); SpawnParameters.Name = "TeleportVisualizer"; - if(BPTeleportVisualizer) + + if (BPTeleportVisualizer) { - TeleportVisualizer = GetWorld()->SpawnActor<AActor>(BPTeleportVisualizer,GetOwner()->GetActorLocation(),GetOwner()->GetActorRotation(),SpawnParameters); + TeleportVisualizer = GetWorld()->SpawnActor<AActor>(BPTeleportVisualizer, VRPawn->GetActorLocation(), + VRPawn->GetActorRotation(), SpawnParameters); } TeleportTraceComponent->SetVisibility(false); TeleportVisualizer->SetActorHiddenInGame(true); - -} - - - -void UTeleportationComponent::SetupInputActions() -{ - Super::SetupInputActions(); - - const AVirtualRealityPawn* VRPawn = Cast<AVirtualRealityPawn>(GetOwner()); - + // simple way of changing the handedness - if(bMoveWithRightHand) + if (bMoveWithRightHand) { TeleportationHand = VRPawn->RightHand; RotationHand = VRPawn->LeftHand; IMCMovement = IMCTeleportRight; - } else + } + else { TeleportationHand = VRPawn->LeftHand; RotationHand = VRPawn->RightHand; IMCMovement = IMCTeleportLeft; } - const APlayerController* PlayerController = Cast<APlayerController>(VRPawn->GetController()); - UEnhancedInputLocalPlayerSubsystem* InputSubsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()); - if(!InputSubsystem) - { - UE_LOG(Toolkit,Error,TEXT("InputSubsystem IS NOT VALID")); - return; - } // add Input Mapping context InputSubsystem->AddMappingContext(IMCMovement,0); - UEnhancedInputComponent* EI = Cast<UEnhancedInputComponent>(VRPawn->InputComponent); - if(!EI) + UEnhancedInputComponent* EI = Cast<UEnhancedInputComponent>(PlayerInputComponent); + if (!EI) { UE_LOG(Toolkit,Error,TEXT("Cannot cast Input Component to Enhanced Inpu Component in VRPawnMovement")); return; @@ -107,8 +95,8 @@ void UTeleportationComponent::OnStartTeleportTrace(const FInputActionValue& Valu void UTeleportationComponent::UpdateTeleportTrace(const FInputActionValue& Value) { // Update the teleport trace - FVector StartPosition = TeleportationHand->GetComponentLocation(); - FVector ForwardVector = TeleportationHand->GetForwardVector(); + const FVector StartPosition = TeleportationHand->GetComponentLocation(); + const FVector ForwardVector = TeleportationHand->GetForwardVector(); TArray<AActor> ActorsToIgnore; @@ -121,33 +109,30 @@ void UTeleportationComponent::UpdateTeleportTrace(const FInputActionValue& Value ECC_WorldStatic ); - PredictParams.ActorsToIgnore.Add(GetOwner()); + PredictParams.ActorsToIgnore.Add(VRPawn); PredictParams.ActorsToIgnore.Add(TeleportVisualizer); UGameplayStatics::PredictProjectilePath(GetWorld(),PredictParams,PredictResult); - - FVector HitLocation = PredictResult.HitResult.Location; - bool bValidHit = PredictResult.HitResult.IsValidBlockingHit(); - // check if this is a valid location to move to - FNavLocation OutLocation; - FNavAgentProperties AgentProperties = FNavAgentProperties(15, 160); + const FVector HitLocation = PredictResult.HitResult.Location; + const bool bValidHit = PredictResult.HitResult.IsValidBlockingHit(); + // check if this is a valid location to move to - UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetCurrent(GetWorld()); - // TODO: does not give valid location - const bool bValidProjection = NavSystem->ProjectPointToNavigation(HitLocation,OutLocation,FVector(1,1,1), &AgentProperties); + FVector OutLocation; + const bool bValidProjection = IsValidTeleportLocation(PredictResult.HitResult, OutLocation); - if(bUseNavMesh) + if (bUseNavMesh) { - FinalTeleportLocation = OutLocation.Location; - if(bValidTeleportLocation != bValidProjection) + FinalTeleportLocation = OutLocation; + if (bValidTeleportLocation != bValidProjection) { bValidTeleportLocation = bValidProjection; TeleportVisualizer->SetActorHiddenInGame(!bValidTeleportLocation); } - } else + } + else { - if(bValidHit) + if (bValidHit) { FinalTeleportLocation = HitLocation; TeleportVisualizer->SetActorHiddenInGame(false); @@ -156,28 +141,36 @@ void UTeleportationComponent::UpdateTeleportTrace(const FInputActionValue& Value } } - TArray<FVector> PathPoints; PathPoints.Add(StartPosition); - for(FPredictProjectilePathPointData PData : PredictResult.PathData) + for (FPredictProjectilePathPointData PData : PredictResult.PathData) { PathPoints.Add(PData.Location); } UNiagaraDataInterfaceArrayFunctionLibrary::SetNiagaraArrayVector(TeleportTraceComponent,FName("User.PointArray"),PathPoints); - +} + +bool UTeleportationComponent::IsValidTeleportLocation(const FHitResult& Hit, FVector& ProjectedLocation) const +{ + UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld()); + const FNavAgentProperties& AgentProps = FNavAgentProperties(15, 160); + FNavLocation ProjectedNavLocation; + const bool bProjectPoint = (NavSys && NavSys->ProjectPointToNavigation(Hit.Location, ProjectedNavLocation, INVALID_NAVEXTENT, &AgentProps)); + ProjectedLocation = ProjectedNavLocation.Location; + return bProjectPoint /*&& Hit.IsValidBlockingHit()*/; } // On button release -> remove trace and teleport user to location void UTeleportationComponent::OnEndTeleportTrace(const FInputActionValue& Value) { - + if (!VRPawn) + return; // End Teleport Trace bTeleportTraceActive = false; TeleportTraceComponent->SetVisibility(false); TeleportVisualizer->SetActorHiddenInGame(true); bValidTeleportLocation = false; - GetOwner()->TeleportTo(FinalTeleportLocation,GetOwner()->GetActorRotation()); - + VRPawn->TeleportTo(FinalTeleportLocation, VRPawn->GetActorRotation()); } diff --git a/Source/RWTHVRToolkit/Private/Pawn/VirtualRealityPawn.cpp b/Source/RWTHVRToolkit/Private/Pawn/VirtualRealityPawn.cpp index 9ad53fb9f005231377b44bf76471a3f19c5ce1d8..4ac07d8efcef2b83aae88b2d4fd6aeec97b2023b 100644 --- a/Source/RWTHVRToolkit/Private/Pawn/VirtualRealityPawn.cpp +++ b/Source/RWTHVRToolkit/Private/Pawn/VirtualRealityPawn.cpp @@ -6,34 +6,38 @@ #include "GameFramework/PlayerController.h" #include "EnhancedInputComponent.h" #include "EnhancedInputSubsystems.h" -#include "MotionControllerComponent.h" -#include "Camera/CameraComponent.h" +#include "ILiveLinkClient.h" +#include "Core/RWTHVRPlayerState.h" +#include "Kismet/GameplayStatics.h" +#include "Logging/StructuredLog.h" +#include "Pawn/ContinuousMovementComponent.h" +#include "Pawn/ReplicatedCameraComponent.h" +#include "Pawn/ReplicatedMotionControllerComponent.h" #include "Pawn/VRPawnInputConfig.h" #include "Pawn/VRPawnMovement.h" +#include "Roles/LiveLinkTransformTypes.h" #include "Utility/VirtualRealityUtilities.h" AVirtualRealityPawn::AVirtualRealityPawn(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { - bUseControllerRotationYaw = true; - bUseControllerRotationPitch = true; - bUseControllerRotationRoll = true; BaseEyeHeight = 160.0f; SetRootComponent(CreateDefaultSubobject<USceneComponent>(TEXT("Origin"))); - - HeadCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera")); + + HeadCameraComponent = CreateDefaultSubobject<UReplicatedCameraComponent>(TEXT("Camera")); HeadCameraComponent->SetupAttachment(RootComponent); - HeadCameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, BaseEyeHeight)); //so it is rendered correctly in editor - + HeadCameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, BaseEyeHeight)); + //so it is rendered correctly in editor + PawnMovement = CreateDefaultSubobject<UVRPawnMovement>(TEXT("Pawn Movement")); PawnMovement->SetUpdatedComponent(RootComponent); PawnMovement->SetHeadComponent(HeadCameraComponent); - - RightHand = CreateDefaultSubobject<UMotionControllerComponent>(TEXT("Right Hand MCC")); + + RightHand = CreateDefaultSubobject<UReplicatedMotionControllerComponent>(TEXT("Right Hand MCC")); RightHand->SetupAttachment(RootComponent); - - LeftHand = CreateDefaultSubobject<UMotionControllerComponent>(TEXT("Left Hand MCC")); + + LeftHand = CreateDefaultSubobject<UReplicatedMotionControllerComponent>(TEXT("Left Hand MCC")); LeftHand->SetupAttachment(RootComponent); BasicVRInteraction = CreateDefaultSubobject<UBasicVRInteractionComponent>(TEXT("Basic VR Interaction")); @@ -49,51 +53,145 @@ void AVirtualRealityPawn::Tick(float DeltaSeconds) SetCameraOffset(); UpdateRightHandForDesktopInteraction(); } + EvaluateLivelink(); +} + +/* + * The alternative would be to do this only on the server on possess and check for player state/type, + * as connections now send their playertype over. + */ +void AVirtualRealityPawn::NotifyControllerChanged() +{ + Super::NotifyControllerChanged(); + + // Only do this for all local controlled pawns + if (IsLocallyControlled()) + { + // Only do this for the master or when we're running in standalone + if (UVirtualRealityUtilities::IsRoomMountedMode() && UVirtualRealityUtilities::IsMaster() || GetNetMode() == + NM_Standalone) + { + if (HasAuthority()) + AttachDCRAtoPawn(); + else + ServerAttachDCRAtoPawnRpc(); + } + } } void AVirtualRealityPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { + Super::SetupPlayerInputComponent(PlayerInputComponent); + APlayerController* PlayerController = Cast<APlayerController>(GetController()); - if (!PlayerController) { - UE_LOG(LogTemp, Error, TEXT("PC Player Controller is invalid")); + if (!PlayerController) + { + UE_LOG(Toolkit, Warning, TEXT("SetupPlayerInputComponent: Player Controller is invalid")); return; } - UEnhancedInputLocalPlayerSubsystem* InputSubsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()); - if(!InputSubsystem) + + // Set the control rotation of the PC to zero again. There is a small period of 2 frames where, when the pawn gets possessed, + // the PC takes on the rotation of the VR Headset ONLY WHEN SPAWNING ON A CLIENT. Reset the rotation here such that + // bUseControllerRotationYaw=true does not pass the wrong yaw value to the pawn initially. + // There is probably a checkbox or way of spawning that prevents that in a better way that this, change if found. + PlayerController->SetControlRotation(FRotator::ZeroRotator); + + const ULocalPlayer* LP = PlayerController->GetLocalPlayer(); + UEnhancedInputLocalPlayerSubsystem* InputSubsystem = LP + ? LP->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>() + : nullptr; + if (!InputSubsystem) { - UE_LOG(Toolkit,Error,TEXT("[VirtualRealiytPawn.cpp] InputSubsystem IS NOT VALID")); + UE_LOG(Toolkit, Error, TEXT("[VirtualRealiytPawn.cpp] InputSubsystem IS NOT VALID")); + return; + } + + SetupMotionControllerSources(); + + // Should not do this here but on connection or on possess I think. + if (ARWTHVRPlayerState* State = GetPlayerState<ARWTHVRPlayerState>()) + { + // Might not be properly synced yet? + const EPlayerType Type = State->GetPlayerType(); + + // Don't do anything with the type if it's been set to clustertype or anything. + const bool bClusterType = Type == EPlayerType::nDisplayPrimary || Type == EPlayerType::nDisplaySecondary; + + if (!bClusterType && UVirtualRealityUtilities::IsHeadMountedMode()) + { + // Could be too early to call this RPC... + State->RequestSetPlayerType(Type); + } } - if(UVirtualRealityUtilities::IsDesktopMode()) + if (UVirtualRealityUtilities::IsDesktopMode()) { PlayerController->bShowMouseCursor = true; PlayerController->bEnableClickEvents = true; PlayerController->bEnableMouseOverEvents = true; } - + InputSubsystem->ClearAllMappings(); // add Input Mapping context - InputSubsystem->AddMappingContext(IMCBase,0); - + InputSubsystem->AddMappingContext(IMCBase, 0); + UEnhancedInputComponent* EI = Cast<UEnhancedInputComponent>(PlayerInputComponent); - + // old function bindings for grabbing and releasing EI->BindAction(Fire, ETriggerEvent::Started, this, &AVirtualRealityPawn::OnBeginFire); EI->BindAction(Fire, ETriggerEvent::Completed, this, &AVirtualRealityPawn::OnEndFire); - EI->BindAction(ToggleNavigationMode,ETriggerEvent::Started,this,&AVirtualRealityPawn::OnToggleNavigationMode); + EI->BindAction(ToggleNavigationMode, ETriggerEvent::Started, this, &AVirtualRealityPawn::OnToggleNavigationMode); + + // Set up mappings on input extension components, need to do this nicely + + for (UActorComponent* Comp : GetComponentsByInterface(UInputExtensionInterface::StaticClass())) + { + Cast<IInputExtensionInterface>(Comp)->SetupPlayerInput(PlayerInputComponent); + } +} + +void AVirtualRealityPawn::EvaluateLivelink() const +{ + if (UVirtualRealityUtilities::IsRoomMountedMode() && IsLocallyControlled()) + { + if (bDisableLiveLink || HeadSubjectRepresentation.Subject.IsNone() || HeadSubjectRepresentation.Role == nullptr) + { + return; + } + + + // Get the LiveLink interface and evaluate the current existing frame data for the given Subject and Role. + ILiveLinkClient& LiveLinkClient = IModularFeatures::Get().GetModularFeature<ILiveLinkClient>( + ILiveLinkClient::ModularFeatureName); + FLiveLinkSubjectFrameData SubjectData; + const bool bHasValidData = LiveLinkClient.EvaluateFrame_AnyThread( + HeadSubjectRepresentation.Subject, HeadSubjectRepresentation.Role, SubjectData); + + if (!bHasValidData) + return; + + // Assume we are using a Transform Role to track the components! This is a slightly dangerous assumption, and could be further improved. + const FLiveLinkTransformStaticData* StaticData = SubjectData.StaticData.Cast<FLiveLinkTransformStaticData>(); + const FLiveLinkTransformFrameData* FrameData = SubjectData.FrameData.Cast<FLiveLinkTransformFrameData>(); + + if (StaticData && FrameData) + { + // Finally, apply the transform to this component according to the static data. + ApplyLiveLinkTransform(FrameData->Transform, *StaticData); + } + } } -void AVirtualRealityPawn::UpdateRightHandForDesktopInteraction() +void AVirtualRealityPawn::UpdateRightHandForDesktopInteraction() const { - APlayerController* PC = Cast<APlayerController>(GetController()); - if (PC) + if (const APlayerController* PC = Cast<APlayerController>(GetController())) { FVector MouseLocation, MouseDirection; PC->DeprojectMousePositionToWorld(MouseLocation, MouseDirection); - FRotator HandOrientation = MouseDirection.ToOrientationRotator(); - if(bMoveRightHandWithMouse) + const FRotator HandOrientation = MouseDirection.ToOrientationRotator(); + if (bMoveRightHandWithMouse) { RightHand->SetWorldRotation(HandOrientation); RightHand->SetRelativeLocation(HeadCameraComponent->GetRelativeLocation()); @@ -106,6 +204,56 @@ void AVirtualRealityPawn::UpdateRightHandForDesktopInteraction() } } +// Todo rewrite this in some other way or attach it differently, this is horrible +void AVirtualRealityPawn::AttachDCRAtoPawn() +{ + if (!CaveSetupActorClass || !CaveSetupActorClass->IsValidLowLevelFast()) + { + UE_LOGFMT(Toolkit, Warning, "No CaveSetup Actor class set in pawn!"); + return; + } + TArray<AActor*> FoundActors; + UGameplayStatics::GetAllActorsOfClass(GetWorld(), CaveSetupActorClass, FoundActors); + + if (!FoundActors.IsEmpty()) + { + const auto CaveSetupActor = FoundActors[0]; + FAttachmentTransformRules AttachmentRules = FAttachmentTransformRules::SnapToTargetNotIncludingScale; + AttachmentRules.RotationRule = EAttachmentRule::KeepWorld; + CaveSetupActor->AttachToActor(this, AttachmentRules); + } + else + { + UE_LOGFMT(Toolkit, Warning, + "No CaveSetup Actor found which can be attached to the Pawn! This won't work on the Cave."); + } +} + +void AVirtualRealityPawn::SetupMotionControllerSources() +{ + // Setup Motion Controllers + + FName MotionControllerSourceLeft = EName::None; + FName MotionControllerSourceRight = EName::None; + if (UVirtualRealityUtilities::IsHeadMountedMode()) + { + MotionControllerSourceLeft = FName("Left"); + MotionControllerSourceRight = FName("Right"); + } + if (UVirtualRealityUtilities::IsRoomMountedMode() && UVirtualRealityUtilities::IsMaster()) + { + MotionControllerSourceLeft = LeftSubjectRepresentation.Subject; + MotionControllerSourceRight = RightSubjectRepresentation.Subject; + } + LeftHand->SetTrackingMotionSource(MotionControllerSourceLeft); + RightHand->SetTrackingMotionSource(MotionControllerSourceRight); +} + +void AVirtualRealityPawn::ServerAttachDCRAtoPawnRpc_Implementation() +{ + AttachDCRAtoPawn(); +} + void AVirtualRealityPawn::SetCameraOffset() const { // this also incorporates the BaseEyeHeight, if set as static offset, @@ -119,40 +267,81 @@ void AVirtualRealityPawn::SetCameraOffset() const // legacy grabbing void AVirtualRealityPawn::OnBeginFire(const FInputActionValue& Value) { - UE_LOG(LogTemp,Warning,TEXT("BeginFire")); + UE_LOG(LogTemp, Warning, TEXT("BeginFire")); BasicVRInteraction->BeginInteraction(); } // legacy grabbing void AVirtualRealityPawn::OnEndFire(const FInputActionValue& Value) { - UE_LOG(Toolkit,Log,TEXT("EndFire")); + UE_LOG(Toolkit, Log, TEXT("EndFire")); BasicVRInteraction->EndInteraction(); } - void AVirtualRealityPawn::OnToggleNavigationMode(const FInputActionValue& Value) { switch (PawnMovement->NavigationMode) { - case EVRNavigationModes::NAV_FLY: - PawnMovement->NavigationMode = EVRNavigationModes::NAV_WALK; - UE_LOG(Toolkit,Log,TEXT("Changed Nav mode to WALK")); - break; - - case EVRNavigationModes::NAV_WALK: - PawnMovement->NavigationMode = EVRNavigationModes::NAV_GHOST; - UE_LOG(Toolkit,Log,TEXT("Changed Nav mode to GHOST")); - break; - case EVRNavigationModes::NAV_GHOST: - PawnMovement->NavigationMode = EVRNavigationModes::NAV_FLY; - UE_LOG(Toolkit, Log, TEXT("Changed Nav mode to FLY")); - break; - default: - PawnMovement->NavigationMode = EVRNavigationModes::NAV_WALK; - UE_LOG(Toolkit,Log,TEXT("Changed Nav mode to WALK")); - break; + case EVRNavigationModes::NAV_FLY: + PawnMovement->NavigationMode = EVRNavigationModes::NAV_WALK; + UE_LOG(Toolkit, Log, TEXT("Changed Nav mode to WALK")); + break; + + case EVRNavigationModes::NAV_WALK: + PawnMovement->NavigationMode = EVRNavigationModes::NAV_GHOST; + UE_LOG(Toolkit, Log, TEXT("Changed Nav mode to GHOST")); + break; + case EVRNavigationModes::NAV_GHOST: + PawnMovement->NavigationMode = EVRNavigationModes::NAV_FLY; + UE_LOG(Toolkit, Log, TEXT("Changed Nav mode to FLY")); + break; + default: + PawnMovement->NavigationMode = EVRNavigationModes::NAV_WALK; + UE_LOG(Toolkit, Log, TEXT("Changed Nav mode to WALK")); + break; } } +void AVirtualRealityPawn::ApplyLiveLinkTransform(const FTransform& Transform, + const FLiveLinkTransformStaticData& StaticData) const +{ + if (StaticData.bIsLocationSupported) + { + if (bWorldTransform) + { + HeadCameraComponent->SetWorldLocation(Transform.GetLocation(), false, nullptr, + ETeleportType::TeleportPhysics); + } + else + { + HeadCameraComponent->SetRelativeLocation(Transform.GetLocation(), false, nullptr, + ETeleportType::TeleportPhysics); + } + } + if (StaticData.bIsRotationSupported) + { + if (bWorldTransform) + { + HeadCameraComponent->SetWorldRotation(Transform.GetRotation(), false, nullptr, + ETeleportType::TeleportPhysics); + } + else + { + HeadCameraComponent->SetRelativeRotation(Transform.GetRotation(), false, nullptr, + ETeleportType::TeleportPhysics); + } + } + + if (StaticData.bIsScaleSupported) + { + if (bWorldTransform) + { + HeadCameraComponent->SetWorldScale3D(Transform.GetScale3D()); + } + else + { + HeadCameraComponent->SetRelativeScale3D(Transform.GetScale3D()); + } + } +} diff --git a/Source/RWTHVRToolkit/Private/Utility/VirtualRealityUtilities.cpp b/Source/RWTHVRToolkit/Private/Utility/VirtualRealityUtilities.cpp index 82cd37c031d5dec8c41ccdd8eb4089b363c3c3c9..2445cacade32d34f6ca149af691255c6ee81d605 100644 --- a/Source/RWTHVRToolkit/Private/Utility/VirtualRealityUtilities.cpp +++ b/Source/RWTHVRToolkit/Private/Utility/VirtualRealityUtilities.cpp @@ -177,42 +177,12 @@ USceneComponent* UVirtualRealityUtilities::GetNamedClusterComponent(const ENamed } } -UEnhancedInputComponent* UVirtualRealityUtilities::GetVRPawnInputComponent(const UWorld* World) -{ - const APawn* VRPawn = Cast<AVirtualRealityPawn>(UGameplayStatics::GetPlayerPawn(World,0)); - if(!VRPawn) - { - UE_LOG(Toolkit, Error,TEXT("[VirtualRealityUtilities.cpp] cannot cast current Pawn to AVirtualRealityPawn")); - return nullptr; - } - return Cast<UEnhancedInputComponent>(VRPawn->InputComponent); -} - -UEnhancedInputLocalPlayerSubsystem* UVirtualRealityUtilities::GetVRPawnLocalPlayerSubsystem(UWorld* World) -{ - const APawn* VRPawn = Cast<AVirtualRealityPawn>(UGameplayStatics::GetPlayerPawn(World,0)); - if(!VRPawn) - { - UE_LOG(Toolkit, Error,TEXT("[VirtualRealityUtilities.cpp] cannot cast current Pawn to AVirtualRealityPawn")); - return nullptr; - } - const APlayerController* PlayerController = Cast<APlayerController>(VRPawn->GetController()); - UEnhancedInputLocalPlayerSubsystem* InputSubsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()); - if(!InputSubsystem) - { - UE_LOG(Toolkit,Error,TEXT("[VirtualRealityUtilities.cpp] InputSubsystem IS NOT VALID")); - return nullptr; - } - return InputSubsystem; - -} - void UVirtualRealityUtilities::ShowErrorAndQuit(UWorld* WorldContext, const FString& Message) { UE_LOG(Toolkit, Error, TEXT("%s"), *Message) #if WITH_EDITOR const FText Title = FText::FromString(FString("RUNTIME ERROR")); - FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Message), &Title); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Message), Title); #endif UKismetSystemLibrary::QuitGame(WorldContext, nullptr, EQuitPreference::Quit, false); diff --git a/Source/RWTHVRToolkit/Public/Core/ClientTransformReplication.h b/Source/RWTHVRToolkit/Public/Core/ClientTransformReplication.h new file mode 100644 index 0000000000000000000000000000000000000000..347a8335a55f42b7b58e79a8ee293c0d7c700515 --- /dev/null +++ b/Source/RWTHVRToolkit/Public/Core/ClientTransformReplication.h @@ -0,0 +1,69 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "Core/VRTransformRep.h" +#include "ClientTransformReplication.generated.h" + + +/* +* Simple Client Transform Replication Component. Replicates the owning actor's root transform from owning client to server, +* from there to all other clients. +*/ +UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) +class RWTHVRTOOLKIT_API UClientTransformReplication : public UActorComponent +{ + GENERATED_BODY() + +public: + UClientTransformReplication(); + +protected: + + /* + * For now, replicate in a naive sending every x ms if the transform has changed. + * This is way overkill, as we should only be sending input. However, I am not yet fully sure how + * the Unreal Client-Authoritative thingy works and what part simulates e.g. gravity. + * As this modifies only the tracking origin, latency should not be that much of an issue, so theoretically + * Server-Authority should work here too, in which case we'd just send the x and y input. + * Try both ways. + */ + + ///////////////////////////////////////////////////////////////////////////////////////////////////// + // Full transform update replication + ///////////////////////////////////////////////////////////////////////////////////////////////////// + + // Rate to update the position to the server, 100htz is default (same as replication rate, should also hit every tick). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Networking", meta = (ClampMin = "0", UIMin = "0")) + float ControllerNetUpdateRate; + + // Accumulates time until next send + float ControllerNetUpdateCount; + + // Replicated transform property - used to replicate from server to all non-owning clients + UPROPERTY(EditDefaultsOnly, ReplicatedUsing = OnRep_ReplicatedTransform, Category = "Networking") + FVRTransformRep ReplicatedTransform; + + // Called whenever ReplicatedTransform is replicated to clients. Not called on Server/Owning client + UFUNCTION() + virtual void OnRep_ReplicatedTransform() + { + // Modify owner position - how does this work in movement components? + // For now, directly apply the transforms: + auto* OwningActor = GetOwner(); + if (OwningActor && OwningActor->HasValidRootComponent()) + OwningActor->SetActorLocationAndRotation(ReplicatedTransform.Position, ReplicatedTransform.Rotation); + } + + // Unreliable Server RPC that sends the transform from owning client to the server + UFUNCTION(Unreliable, Server, WithValidation) + void SendControllerTransform_ServerRpc(FVRTransformRep NewTransform); + + void UpdateState(float DeltaTime); + +public: + virtual void TickComponent(float DeltaTime, ELevelTick TickType, + FActorComponentTickFunction* ThisTickFunction) override; +}; diff --git a/Source/RWTHVRToolkit/Public/Core/PlayerType.h b/Source/RWTHVRToolkit/Public/Core/PlayerType.h new file mode 100644 index 0000000000000000000000000000000000000000..8364bd2df3751604ed28b12b8b4ff15d28ab3ce6 --- /dev/null +++ b/Source/RWTHVRToolkit/Public/Core/PlayerType.h @@ -0,0 +1,20 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" + +#include "PlayerType.generated.h" + +/** + * Enum that defines the various player types. Potentially this could be changed into the Enum/FName hybrid that's often + * used, which would make this runtime extendable. Probably unnecessary. + */ +UENUM(BlueprintType) +enum class EPlayerType : uint8 +{ + nDisplayPrimary UMETA(DisplayName = "nDisplay Primary"), + nDisplaySecondary UMETA(DisplayName = "nDisplay Secondary"), + Desktop UMETA(DisplayName = "Desktop"), + HMD UMETA(DisplayName = "HMD") +}; diff --git a/Source/RWTHVRToolkit/Public/Core/RWTHVRGameModeBase.h b/Source/RWTHVRToolkit/Public/Core/RWTHVRGameModeBase.h new file mode 100644 index 0000000000000000000000000000000000000000..ba4db0209058f6d813a72a26519f7f79992cba85 --- /dev/null +++ b/Source/RWTHVRToolkit/Public/Core/RWTHVRGameModeBase.h @@ -0,0 +1,22 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/GameModeBase.h" +#include "RWTHVRGameModeBase.generated.h" + +/** + * Simple GameModeBase extension that checks for join options such that we can distinguish between primary + * and secondary nodes in clusters. Could be moved to a different place as well, but quite reasonable here for now. + * Keep in mind that GameMode only exists on the server! + */ +UCLASS() +class RWTHVRTOOLKIT_API ARWTHVRGameModeBase : public AGameModeBase +{ + GENERATED_BODY() + +protected: + virtual FString InitNewPlayer(APlayerController* NewPlayerController, const FUniqueNetIdRepl& UniqueId, + const FString& Options, const FString& Portal) override; +}; diff --git a/Source/RWTHVRToolkit/Public/Core/RWTHVRPlayerState.h b/Source/RWTHVRToolkit/Public/Core/RWTHVRPlayerState.h new file mode 100644 index 0000000000000000000000000000000000000000..4f9cc9d5639c09587cab91c1ed0bd9dcc0bc0ec3 --- /dev/null +++ b/Source/RWTHVRToolkit/Public/Core/RWTHVRPlayerState.h @@ -0,0 +1,45 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "PlayerType.h" +#include "GameFramework/PlayerState.h" +#include "RWTHVRPlayerState.generated.h" + +enum class EPlayerType : uint8; +/** + * Extension of the PlayerState that additionally holds information about what type the player is. + * E.g. nDisplayPrimary, nDisplaySecondary, Regular (ideally split into HMD | Desktop etc) + */ +UCLASS() +class RWTHVRTOOLKIT_API ARWTHVRPlayerState : public APlayerState +{ + GENERATED_BODY() + +private: + /** Replicated player type for this player*/ + UPROPERTY(Replicated, Category=PlayerState, BlueprintGetter=GetPlayerType, meta=(AllowPrivateAccess)) + EPlayerType PlayerType = EPlayerType::Desktop; + + UFUNCTION(Reliable, Server) + void SetPlayerTypeServerRpc(EPlayerType NewPlayerType); + + void SetPlayerType(EPlayerType NewPlayerType); + +public: + + UFUNCTION(BlueprintGetter) + EPlayerType GetPlayerType() const + { + return PlayerType; + } + + UFUNCTION(BlueprintCallable) + void RequestSetPlayerType(EPlayerType NewPlayerType); + + virtual void CopyProperties(APlayerState* PlayerState) override; + virtual void OverrideWith(APlayerState* PlayerState) override; + + virtual void GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const override; +}; diff --git a/Source/RWTHVRToolkit/Public/Fixes/LiveLinkMotionControllerFix.h b/Source/RWTHVRToolkit/Public/Fixes/LiveLinkMotionControllerFix.h index 3e8bb02b97f5c333281fd3b23df5109f9370b1ce..208a0ad0d52eb3a99e1437bf0c8088f038862921 100644 --- a/Source/RWTHVRToolkit/Public/Fixes/LiveLinkMotionControllerFix.h +++ b/Source/RWTHVRToolkit/Public/Fixes/LiveLinkMotionControllerFix.h @@ -13,6 +13,15 @@ #define LOCTEXT_NAMESPACE "LiveLinkMotionController" +/* + * Workaround due to an issue regarding LiveLInk Subjects as MotionController sources: + * The original FLiveLinkMotionController updates its subject list OnLiveLinkSourcesChanged + * instead of OnLiveLinkSubjectsChanged, which leads to the subjects not being actually initialized when queried. + * As they are a modular feature, this is a copy & paste of the original class with just that simple line changed. + * Works well non-invasive and can be removed if the PR is merged or they fix it another way: + * https://github.com/EpicGames/UnrealEngine/pull/10895 +*/ + class FLiveLinkMotionControllerFix : public IMotionController { // Internal structure for caching enumerated data diff --git a/Source/RWTHVRToolkit/Public/Pawn/BasicVRInteractionComponent.h b/Source/RWTHVRToolkit/Public/Pawn/BasicVRInteractionComponent.h index e62fbf21485da813efa4317cd553169af4beb7a0..bf62656ecf13cb0e336c201ffc11d40c9a58ca0f 100644 --- a/Source/RWTHVRToolkit/Public/Pawn/BasicVRInteractionComponent.h +++ b/Source/RWTHVRToolkit/Public/Pawn/BasicVRInteractionComponent.h @@ -19,7 +19,7 @@ enum EInteractionRayVisibility }; -UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) class RWTHVRTOOLKIT_API UBasicVRInteractionComponent : public UWidgetInteractionComponent { GENERATED_BODY() @@ -28,53 +28,76 @@ public: // Sets default values for this component's properties UBasicVRInteractionComponent(); - void BeginPlay() override; + virtual void BeginPlay() override; - UFUNCTION(BlueprintCallable) void BeginInteraction(); - UFUNCTION(BlueprintCallable) void EndInteraction(); - - // Called every frame virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; - UPROPERTY(EditAnywhere,BlueprintReadWrite) float MaxGrabDistance = 50; - UPROPERTY(EditAnywhere,BlueprintReadWrite) float MaxClickDistance = 500; - // Enable this if you want to interact with Targetable classes or use EInteractionRayVisibility::VisibleOnHoverOnly - UPROPERTY(EditAnywhere) bool bCanRaytraceEveryTick = false; - UPROPERTY(EditAnywhere) TEnumAsByte<EInteractionRayVisibility> InteractionRayVisibility = EInteractionRayVisibility::Invisible; + UFUNCTION(BlueprintCallable) + void BeginInteraction(); + + UFUNCTION(BlueprintCallable) + void EndInteraction(); - UFUNCTION(BlueprintCallable) void Initialize(USceneComponent* RayEmitter); + UFUNCTION(BlueprintCallable) + void Initialize(USceneComponent* RayEmitter); - UFUNCTION(BlueprintCallable, BlueprintPure) AActor* GetGrabbedActor() const { return GrabbedActor;} - UFUNCTION(BlueprintCallable, BlueprintPure) USceneComponent* GetInteractionRayEmitter() const { return InteractionRayEmitter; } + UFUNCTION(BlueprintCallable, BlueprintPure) + AActor* GetGrabbedActor() const { return GrabbedActor;} + + UFUNCTION(BlueprintCallable, BlueprintPure) + USceneComponent* GetInteractionRayEmitter() const { return InteractionRayEmitter; } + + UFUNCTION(BlueprintCallable) + void SetInteractionRayVisibility(EInteractionRayVisibility NewVisibility); - UFUNCTION(BlueprintCallable) void SetInteractionRayVisibility(EInteractionRayVisibility NewVisibility); + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pawn|Interaction") + UStaticMeshComponent* InteractionRay; + + UPROPERTY(EditAnywhere,BlueprintReadWrite) + float MaxGrabDistance = 50; + + UPROPERTY(EditAnywhere,BlueprintReadWrite) + float MaxClickDistance = 500; + + // Enable this if you want to interact with Targetable classes or use EInteractionRayVisibility::VisibleOnHoverOnly + UPROPERTY(EditAnywhere) + bool bCanRaytraceEveryTick = false; + + UPROPERTY(EditAnywhere) + TEnumAsByte<EInteractionRayVisibility> InteractionRayVisibility = EInteractionRayVisibility::Invisible; + private: /* Holding a reference to the actor that is currently being grabbed */ - UPROPERTY() AActor* GrabbedActor; + UPROPERTY() + AActor* GrabbedActor; + /* Holds a reference to the grabbed actors physics simulating component if there was one*/ - UPROPERTY() UPrimitiveComponent* ComponentSimulatingPhysics = nullptr; - UPROPERTY() UGrabbingBehaviorComponent* Behavior = nullptr; - UPROPERTY() USceneComponent* InteractionRayEmitter = nullptr; - UPROPERTY() UStaticMeshComponent* InteractionRay = nullptr; + UPROPERTY() + UPrimitiveComponent* ComponentSimulatingPhysics = nullptr; + + UPROPERTY() + UGrabbingBehaviorComponent* Behavior = nullptr; + + UPROPERTY() + USceneComponent* InteractionRayEmitter = nullptr; /* Stores the reference of the Actor that was hit in the last frame*/ - UPROPERTY() AActor* LastActorHit = nullptr; - void HandlePhysicsAndAttachActor(AActor* HitActor); + UPROPERTY() + AActor* LastActorHit = nullptr; + + void HandlePhysicsAndAttachActor(const AActor* HitActor); FTwoVectors GetHandRay(float Length) const; TOptional<FHitResult> RaytraceForFirstHit(const FTwoVectors& Ray) const; -}; - -// Free utility functions -/* - Returns the UPrimitiveComponent simulating physics that is highest in the hierarchy -*/ -UPrimitiveComponent* GetFirstComponentSimulatingPhysics(const AActor* TargetActor); -/* - Recursive Function - If parent component simulates physics returns GetHighestParentSimulatingPhysics(Parent) - else returns Comp itself -*/ -UPrimitiveComponent* GetHighestParentSimulatingPhysics(UPrimitiveComponent* Comp); - - + // Free utility functions + /* + Returns the UPrimitiveComponent simulating physics that is highest in the hierarchy + */ + static UPrimitiveComponent* GetFirstComponentSimulatingPhysics(const AActor* TargetActor); + /* + Recursive Function + If parent component simulates physics returns GetHighestParentSimulatingPhysics(Parent) + else returns Comp itself + */ + static UPrimitiveComponent* GetHighestParentSimulatingPhysics(UPrimitiveComponent* Comp); +}; diff --git a/Source/RWTHVRToolkit/Public/Pawn/ContinuousMovementComponent.h b/Source/RWTHVRToolkit/Public/Pawn/ContinuousMovementComponent.h index 586076048e6a196b10008111526b78a996cda26c..e4ea442da36c0d766be850c290f29183f7835184 100644 --- a/Source/RWTHVRToolkit/Public/Pawn/ContinuousMovementComponent.h +++ b/Source/RWTHVRToolkit/Public/Pawn/ContinuousMovementComponent.h @@ -3,6 +3,7 @@ #pragma once #include "CoreMinimal.h" +#include "InputExtensionInterface.h" #include "Pawn/VirtualRealityPawn.h" #include "Pawn/MovementComponentBase.h" #include "Components/ActorComponent.h" @@ -25,27 +26,24 @@ class RWTHVRTOOLKIT_API UContinuousMovementComponent : public UMovementComponent GENERATED_BODY() public: - - virtual void BeginPlay() override; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VR Movement") EVRSteeringModes SteeringMode = EVRSteeringModes::STEER_HAND_DIRECTED; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VR Movement") bool bMoveWithRightHand = true; - - + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VR Movement|Input") - class UInputMappingContext* IMCMovementLeft; + UInputMappingContext* IMCMovementLeft; UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VR Movement|Input") - class UInputMappingContext* IMCMovementRight; + UInputMappingContext* IMCMovementRight; UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VR Movement|Input|Actions") - class UInputAction* Move; + UInputAction* Move; UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VR Movement|Input|Actions") - class UInputAction* MoveUp; + UInputAction* MoveUp; /*Movement Input*/ UFUNCTION(BlueprintCallable) @@ -53,7 +51,8 @@ public: UFUNCTION(BlueprintCallable) void OnMoveUp(const FInputActionValue& Value); - + + virtual void SetupPlayerInput(UInputComponent* PlayerInputComponent) override; private: @@ -64,8 +63,5 @@ private: UMotionControllerComponent* MovementHand; UPROPERTY() - UMotionControllerComponent* RotationHand; - - - virtual void SetupInputActions(); + UMotionControllerComponent* RotationHand; }; diff --git a/Source/RWTHVRToolkit/Public/Pawn/InputExtensionInterface.h b/Source/RWTHVRToolkit/Public/Pawn/InputExtensionInterface.h new file mode 100644 index 0000000000000000000000000000000000000000..543fe244a7566c34651c353b3b60f392aa90f9e9 --- /dev/null +++ b/Source/RWTHVRToolkit/Public/Pawn/InputExtensionInterface.h @@ -0,0 +1,32 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "UObject/Interface.h" +#include "InputExtensionInterface.generated.h" + +class UEnhancedInputLocalPlayerSubsystem; + +/** + * Simple interface that can be implemented if your class/component defines input actions. + * Use SetupPlayerInput to bind actions, it will be called by VirtualRealityPawn::SetupPlayerInputComponent. + */ +UINTERFACE(MinimalAPI, NotBlueprintable) +class UInputExtensionInterface : public UInterface +{ + GENERATED_BODY() +}; + +class RWTHVRTOOLKIT_API IInputExtensionInterface +{ + GENERATED_BODY() + +public: + + // Called by VirtualRealityPawn::SetupPlayerInputComponent + virtual void SetupPlayerInput(UInputComponent* PlayerInputComponent) {} + + // Helper function to get the local player subsystem + virtual UEnhancedInputLocalPlayerSubsystem* GetEnhancedInputLocalPlayerSubsystem(APawn* Pawn) const; + +}; diff --git a/Source/RWTHVRToolkit/Public/Pawn/ReplicatedCameraComponent.h b/Source/RWTHVRToolkit/Public/Pawn/ReplicatedCameraComponent.h new file mode 100644 index 0000000000000000000000000000000000000000..b211d827423d8497ea2231354160f3b892e4c86a --- /dev/null +++ b/Source/RWTHVRToolkit/Public/Pawn/ReplicatedCameraComponent.h @@ -0,0 +1,54 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Camera/CameraComponent.h" +#include "Core/VRTransformRep.h" +#include "ReplicatedCameraComponent.generated.h" + +/** + * Simple CameraComponent with added client-side transform replication. + */ +UCLASS() +class RWTHVRTOOLKIT_API UReplicatedCameraComponent : public UCameraComponent +{ + GENERATED_BODY() +public: + // Sets default values for this component's properties + UReplicatedCameraComponent(); + +protected: + + ///////////////////////////////////////////////////////////////////////////////////////////////////// + // Full transform update replication + ///////////////////////////////////////////////////////////////////////////////////////////////////// + + // Rate to update the position to the server, 100htz is default (same as replication rate, should also hit every tick). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Networking", meta = (ClampMin = "0", UIMin = "0")) + float ControllerNetUpdateRate; + + // Accumulates time until next send + float ControllerNetUpdateCount; + + UPROPERTY(EditDefaultsOnly, ReplicatedUsing = OnRep_ReplicatedTransform, Category = "Networking") + FVRTransformRep ReplicatedTransform; + + void UpdateState(float DeltaTime); + + UFUNCTION() + virtual void OnRep_ReplicatedTransform() + { + //For now, directly apply the transforms: + if (!GetOwner()->HasLocalNetOwner()) + SetRelativeLocationAndRotation(ReplicatedTransform.Position, ReplicatedTransform.Rotation); + } + + UFUNCTION(Unreliable, Server, WithValidation) + void ServerSendControllerTransformRpc(FVRTransformRep NewTransform); + +public: + // Called every frame + virtual void TickComponent(float DeltaTime, ELevelTick TickType, + FActorComponentTickFunction* ThisTickFunction) override; +}; diff --git a/Source/RWTHVRToolkit/Public/Pawn/ReplicatedMotionControllerComponent.h b/Source/RWTHVRToolkit/Public/Pawn/ReplicatedMotionControllerComponent.h new file mode 100644 index 0000000000000000000000000000000000000000..1d77aa634e2e74f0d35b24ab0ec131933fd81c4b --- /dev/null +++ b/Source/RWTHVRToolkit/Public/Pawn/ReplicatedMotionControllerComponent.h @@ -0,0 +1,55 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "MotionControllerComponent.h" +#include "Core/VRTransformRep.h" +#include "ReplicatedMotionControllerComponent.generated.h" + +/** + * Simple MotionControllerComponent with added client-side transform replication. + */ +UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) +class RWTHVRTOOLKIT_API UReplicatedMotionControllerComponent : public UMotionControllerComponent +{ + GENERATED_BODY() + +public: + // Sets default values for this component's properties + UReplicatedMotionControllerComponent(); + +protected: + + ///////////////////////////////////////////////////////////////////////////////////////////////////// + // Full transform update replication + ///////////////////////////////////////////////////////////////////////////////////////////////////// + + // Rate to update the position to the server, 100htz is default (same as replication rate, should also hit every tick). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Networking", meta = (ClampMin = "0", UIMin = "0")) + float ControllerNetUpdateRate; + + // Accumulates time until next send + float ControllerNetUpdateCount; + + UPROPERTY(EditDefaultsOnly, ReplicatedUsing = OnRep_ReplicatedTransform, Category = "Networking") + FVRTransformRep ReplicatedTransform; + + void UpdateState(float DeltaTime); + + UFUNCTION() + virtual void OnRep_ReplicatedTransform() + { + // For now, directly apply the transforms: + SetRelativeLocationAndRotation(ReplicatedTransform.Position, ReplicatedTransform.Rotation); + } + + UFUNCTION(Unreliable, Server, WithValidation) + void SendControllerTransform_ServerRpc(FVRTransformRep NewTransform); + +public: + // Called every frame + virtual void TickComponent(float DeltaTime, ELevelTick TickType, + FActorComponentTickFunction* ThisTickFunction) override; + +}; diff --git a/Source/RWTHVRToolkit/Public/Pawn/TeleportationComponent.h b/Source/RWTHVRToolkit/Public/Pawn/TeleportationComponent.h index 54c3389aededa63c4bcaa80ed2d05262d2a00e1b..867c122f412b8f2834285287a27d05d63f16ae4a 100644 --- a/Source/RWTHVRToolkit/Public/Pawn/TeleportationComponent.h +++ b/Source/RWTHVRToolkit/Public/Pawn/TeleportationComponent.h @@ -3,27 +3,21 @@ #pragma once #include "CoreMinimal.h" +#include "InputExtensionInterface.h" #include "Components/ActorComponent.h" #include "Pawn/VirtualRealityPawn.h" #include "NiagaraComponent.h" #include "Kismet/GameplayStaticsTypes.h" #include "Pawn/MovementComponentBase.h" - #include "TeleportationComponent.generated.h" - UCLASS(Blueprintable) class RWTHVRTOOLKIT_API UTeleportationComponent : public UMovementComponentBase { GENERATED_BODY() -protected: - // Called when the game starts - virtual void BeginPlay() override; - public: - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VR Movement") bool bMoveWithRightHand = true; @@ -34,7 +28,6 @@ public: UPROPERTY(VisibleAnywhere, Category = "VR Movement|Teleport") bool bUseNavMesh = false; - /** * Speed at which the projectile shoots out from the controller to get the teleport location * Higher values = larger teleportation range @@ -43,13 +36,23 @@ public: float TeleportLaunchSpeed = 800; UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VR Movement|Input") - class UInputMappingContext* IMCTeleportLeft; + UInputMappingContext* IMCTeleportLeft; UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VR Movement|Input") - class UInputMappingContext* IMCTeleportRight; + UInputMappingContext* IMCTeleportRight; UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VR Movement|Input|Actions") - class UInputAction* Move; + UInputAction* Move; + + // Trace Visualization + UPROPERTY(EditAnywhere) + TSubclassOf<AActor> BPTeleportVisualizer; + + UPROPERTY(EditDefaultsOnly) + UNiagaraSystem* TeleportTraceSystem; + + UPROPERTY() + UNiagaraComponent* TeleportTraceComponent; /*Movement Input*/ UFUNCTION(BlueprintCallable) @@ -57,20 +60,13 @@ public: UFUNCTION(BlueprintCallable) void UpdateTeleportTrace(const FInputActionValue& Value); + bool IsValidTeleportLocation(const FHitResult& Hit, FVector& ProjectedLocation) const; UFUNCTION(BlueprintCallable) void OnEndTeleportTrace(const FInputActionValue& Value); - // Trace Visualization - UPROPERTY(EditAnywhere) - TSubclassOf<AActor> BPTeleportVisualizer; - - UPROPERTY(EditDefaultsOnly) - UNiagaraSystem* TeleportTraceSystem; + virtual void SetupPlayerInput(UInputComponent* PlayerInputComponent) override; - UPROPERTY() - UNiagaraComponent* TeleportTraceComponent; - private: UPROPERTY() UMotionControllerComponent* TeleportationHand; @@ -79,10 +75,8 @@ private: UMotionControllerComponent* RotationHand; UPROPERTY() - class UInputMappingContext* IMCMovement; - - virtual void SetupInputActions(); - + UInputMappingContext* IMCMovement; + bool bTeleportTraceActive; float TeleportProjectileRadius = 3.6; float RotationArrowRadius = 10.0; @@ -92,5 +86,4 @@ private: UPROPERTY() AActor* TeleportVisualizer; - }; diff --git a/Source/RWTHVRToolkit/Public/Pawn/VirtualRealityPawn.h b/Source/RWTHVRToolkit/Public/Pawn/VirtualRealityPawn.h index 1c85f8cf157376ddb7665bb3a02bc04a759abdcd..9238d465709831c05e9f014710a5738e5e532b32 100644 --- a/Source/RWTHVRToolkit/Public/Pawn/VirtualRealityPawn.h +++ b/Source/RWTHVRToolkit/Public/Pawn/VirtualRealityPawn.h @@ -4,42 +4,44 @@ #include "BasicVRInteractionComponent.h" #include "CoreMinimal.h" +#include "LiveLinkRole.h" #include "Pawn/VRPawnMovement.h" #include "VirtualRealityPawn.generated.h" +class UInputMappingContext; +class UInputAction; class UCameraComponent; -class ULiveLinkComponentController; class UMotionControllerComponent; +struct FLiveLinkTransformStaticData; /** - * + * Pawn implementation with additional VR functionality, can be used in the Cave, with an HMD and on desktop. */ UCLASS(Abstract) class RWTHVRTOOLKIT_API AVirtualRealityPawn : public APawn { GENERATED_BODY() + public: AVirtualRealityPawn(const FObjectInitializer& ObjectInitializer); virtual void Tick(float DeltaSeconds) override; - + + virtual void NotifyControllerChanged() override; + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pawn|MotionControllers") UMotionControllerComponent* RightHand; - + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pawn|MotionControllers") UMotionControllerComponent* LeftHand; /* Interaction */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pawn|Interaction") UBasicVRInteractionComponent* BasicVRInteraction; - + /* Movement */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pawn|Movement") UVRPawnMovement* PawnMovement; - - /* CameraComponent */ - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pawn|Camera") - UCameraComponent* HeadCameraComponent; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pawn|Desktop Movement") bool bMoveRightHandWithMouse = true; @@ -47,34 +49,78 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pawn|Desktop Movement") bool bMoveLeftHandWithMouse = false; + /* CameraComponent */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pawn|Camera") + UCameraComponent* HeadCameraComponent; + + // LiveLink functionality + + /* Set whether nDisplay should disable LiveLink tracking*/ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pawn|LiveLink") + bool bDisableLiveLink = false; + + /* Set the LiveLink Subject Representation to be used by this pawn. */ + UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Pawn|LiveLink") + FLiveLinkSubjectRepresentation HeadSubjectRepresentation; + + /* Set the LiveLink Subject Representation to be used by this pawn. */ + UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Pawn|LiveLink") + FLiveLinkSubjectRepresentation LeftSubjectRepresentation; + + /* Set the LiveLink Subject Representation to be used by this pawn. */ + UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Pawn|LiveLink") + FLiveLinkSubjectRepresentation RightSubjectRepresentation; + + /* Set the transform of the component in world space of in its local reference frame. */ + 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; + /* LiveLink helper function called on tick */ + void EvaluateLivelink() const; + + /* Helper function that applies the LiveLink data to this component. Taken from the LiveLink Transform Controller. */ + void ApplyLiveLinkTransform(const FTransform& Transform, const FLiveLinkTransformStaticData& StaticData) const; + /* Interaction */ UFUNCTION(BlueprintCallable, Category = "Pawn|Interaction") void OnBeginFire(const FInputActionValue& Value); - + UFUNCTION(BlueprintCallable, Category = "Pawn|Interaction") void OnEndFire(const FInputActionValue& Value); UFUNCTION(BlueprintCallable) void OnToggleNavigationMode(const FInputActionValue& Value); - + /* Input */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Pawn|Input") - class UInputMappingContext* IMCBase; - + UInputMappingContext* IMCBase; + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Pawn|Input") - class UInputAction* Fire; + UInputAction* Fire; UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Pawn|Input") - class UInputAction* ToggleNavigationMode; + UInputAction* ToggleNavigationMode; - /** - * Fixes camera rotation in desktop mode. - */ + /* Fixes camera rotation in desktop mode. */ void SetCameraOffset() const; - void UpdateRightHandForDesktopInteraction(); + void UpdateRightHandForDesktopInteraction() const; + + /* Replicated functionality */ + + /* Ask the server to attach the DCRA to the correct pawn */ + UFUNCTION(Reliable, Server) + void ServerAttachDCRAtoPawnRpc(); + + /* Attaches the DCRA to the pawn */ + void AttachDCRAtoPawn(); + /* Set device specific motion controller sources (None, L/R, Livelink) */ + void SetupMotionControllerSources(); }; diff --git a/Source/RWTHVRToolkit/Public/Utility/VirtualRealityUtilities.h b/Source/RWTHVRToolkit/Public/Utility/VirtualRealityUtilities.h index 831f9a1143bc81d51434677783f133186fe12c45..67169f9935f37ae034e97e112e09e977fe61a154 100644 --- a/Source/RWTHVRToolkit/Public/Utility/VirtualRealityUtilities.h +++ b/Source/RWTHVRToolkit/Public/Utility/VirtualRealityUtilities.h @@ -13,7 +13,6 @@ */ DECLARE_LOG_CATEGORY_EXTERN(Toolkit, Log, All); - UENUM(BlueprintType) enum class ENamedClusterComponent : uint8 { @@ -70,19 +69,15 @@ public: //Get Component of Display Cluster by it's name, which is specified in the nDisplay config UFUNCTION(BlueprintPure, BlueprintCallable, Category = "DisplayCluster") static USceneComponent* GetClusterComponent(const FString& Name); UFUNCTION(BlueprintPure, BlueprintCallable, Category = "DisplayCluster") static USceneComponent* GetNamedClusterComponent(const ENamedClusterComponent& Component); - - // Helper functions for components to register input events - UFUNCTION(BlueprintPure, BlueprintCallable, Category = "VRPawn") - static UEnhancedInputComponent* GetVRPawnInputComponent(const UWorld* World); - UFUNCTION(BlueprintPure, BlueprintCallable, Category = "VRPawn") - static UEnhancedInputLocalPlayerSubsystem* GetVRPawnLocalPlayerSubsystem(UWorld* World); /* Load and create an Object from an asset path. This only works in the constructor */ template <class T> + [[deprecated]] static bool LoadAsset(const FString& Path, T*& Result); - + /* Finds and returns a class of an asset. This only works in the constructor */ template <class T> + [[deprecated]] static bool LoadClass(const FString& Path, TSubclassOf<T>& Result); UFUNCTION(BlueprintCallable) diff --git a/Source/RWTHVRToolkit/RWTHVRToolkit.Build.cs b/Source/RWTHVRToolkit/RWTHVRToolkit.Build.cs index d68c4ffc1ab3c60308bc525b24edc6d4c7bb92a6..930dc9129b0fbcf2ba9b275595cea42f42866238 100644 --- a/Source/RWTHVRToolkit/RWTHVRToolkit.Build.cs +++ b/Source/RWTHVRToolkit/RWTHVRToolkit.Build.cs @@ -36,7 +36,10 @@ public class RWTHVRToolkit : ModuleRules ); PrivateDependencyModuleNames.AddRange( - new string[]{} + new string[] + { + "NetCore" + } ); if (Target.bBuildEditor == true) {