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)
{