Skip to content
Snippets Groups Projects
Commit 1e67c0d8 authored by David Gilbert's avatar David Gilbert :bug:
Browse files

Initial separation

parents
Branches
No related tags found
No related merge requests found
Pipeline #350009 failed
Showing
with 641 additions and 0 deletions
// Fill out your copyright notice in the Description page of Project Settings.
#include "CaveSetup.h"
#include "Logging/StructuredLog.h"
#include "Utility/RWTHVRUtilities.h"
// Sets default values
ACaveSetup::ACaveSetup()
{
PrimaryActorTick.bCanEverTick = false;
SetActorEnableCollision(false);
// Actor needs to replicate, as it is attached to the pawn on the server.
bReplicates = true;
}
// Called when the game starts or when spawned
void ACaveSetup::BeginPlay()
{
Super::BeginPlay();
if (!URWTHVRUtilities::IsRoomMountedMode())
{
return;
}
// Spawn all actors that are set in the blueprint asset.
for (const auto ActorClass : ActorsToSpawnOnCave)
{
if (const auto World = GetWorld())
{
const auto Actor = World->SpawnActor(ActorClass);
Actor->AttachToActor(this, FAttachmentTransformRules::SnapToTargetNotIncludingScale);
UE_LOGFMT(LogTemp, Display, "CaveSetup: Spawned Actor {Actor} on the Cave and attached it.",
Actor->GetName());
}
}
// Apply the DTrack LiveLink Preset. Only do this if we are the primaryNode
if (URWTHVRUtilities::IsPrimaryNode())
{
if (LiveLinkPresetToApplyOnCave && LiveLinkPresetToApplyOnCave->IsValidLowLevelFast())
{
LiveLinkPresetToApplyOnCave->ApplyToClientLatent();
UE_LOGFMT(LogTemp, Display, "CaveSetup: Applied LiveLinkPreset {Preset} to Client.",
LiveLinkPresetToApplyOnCave->GetName());
}
}
}
#include "ClusterConsole/ClusterConsole.h"
#include "IDisplayCluster.h"
#include "Cluster/DisplayClusterClusterEvent.h"
void FClusterConsole::Register()
{
/* Registering console command */
ClusterConsoleCommand = IConsoleManager::Get().RegisterConsoleCommand(
TEXT("ClusterExecute"),
TEXT("<Your Command> - Execute commands on every node of the nDisplay cluster by prepending ClusterExecute"),
FConsoleCommandWithArgsDelegate::CreateLambda(
[](const TArray<FString>& Args)
{
if (IDisplayCluster::Get().GetClusterMgr() == nullptr || Args.Num() == 0)
return;
/* Emitting cluster event */
FDisplayClusterClusterEventJson ClusterEvent;
ClusterEvent.Name = "ClusterExecute " + Args[0];
ClusterEvent.Type = Args[0];
ClusterEvent.Category = "NDisplayClusterExecute";
ClusterEvent.Parameters.Add("Command", FString::Join(Args, TEXT(" ")));
IDisplayCluster::Get().GetClusterMgr()->EmitClusterEventJson(ClusterEvent, false);
}));
/* Register cluster event handling */
const IDisplayCluster* DisplayCluster = FModuleManager::LoadModulePtr<IDisplayCluster>(IDisplayCluster::ModuleName);
if (DisplayCluster && !ClusterEventListenerDelegate.IsBound())
{
ClusterEventListenerDelegate = FOnClusterEventJsonListener::CreateLambda(
[](const FDisplayClusterClusterEventJson& Event)
{
/* Actual handling */
if (Event.Category.Equals("NDisplayClusterExecute") && Event.Parameters.Contains("Command") && GEngine)
{
GEngine->Exec(GEngine->GetCurrentPlayWorld(), *Event.Parameters["Command"]);
}
});
DisplayCluster->GetClusterMgr()->AddClusterEventJsonListener(ClusterEventListenerDelegate);
}
}
void FClusterConsole::Unregister() const
{
IConsoleManager::Get().UnregisterConsoleObject(ClusterConsoleCommand);
IDisplayCluster::Get().GetClusterMgr()->RemoveClusterEventJsonListener(ClusterEventListenerDelegate);
}
#include "RWTHVRCluster.h"
#define LOCTEXT_NAMESPACE "FRWTHVRClusterModule"
void FRWTHVRClusterModule::StartupModule() { ClusterConsole.Register(); }
void FRWTHVRClusterModule::ShutdownModule() { ClusterConsole.Unregister(); }
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FRWTHVRClusterModule, RWTHVRCluster)
#pragma once
#include "CoreMinimal.h"
#include "InputAction.h"
#include "GameFramework/Actor.h"
#include "CAVEOverlay/DoorOverlayData.h"
#include "Events/DisplayClusterEventWrapper.h"
#include "CAVEOverlayController.generated.h"
class ARWTHVRPawn;
DECLARE_LOG_CATEGORY_EXTERN(LogCAVEOverlay, Log, All);
/**
* Actor which controls the cave overlay. The overlay displays a warning tape around the cave
* when the user moves their head too close to the wall, and a warning sign when the hands are
* too close.
*/
UCLASS()
class RWTHVRCLUSTER_API ACAVEOverlayController : public AActor
{
GENERATED_BODY()
public:
ACAVEOverlayController();
protected:
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
private:
// Types of cave screens defined in the cluster config.
enum EScreen_Type
{
// the primary node screen
SCREEN_PRIMARY,
// any secondary node screen
SCREEN_NORMAL,
// the screens that cover the partially opened door
SCREEN_DOOR_PARTIAL,
// additional screens that cover the door
SCREEN_DOOR
};
// which screen type this node is running on
EScreen_Type ScreenType = SCREEN_NORMAL;
// which additional node names define the screens that cover the door
const TArray<FString> ScreensDoor = {"node_bul_left_eye", "node_bul_right_eye", "node_bll_left_eye",
"node_bll_right_eye"};
// which node names define the screens that cover the partial door
const TArray<FString> ScreensDoorPartial = {"node_bur_left_eye", "node_bur_right_eye", "node_blr_left_eye",
"node_blr_right_eye"};
const FString ScreenMain = "node_main";
// Door Mode
enum EDoorMode
{
DOOR_PARTIALLY_OPEN = 0,
DOOR_OPEN = 1,
DOOR_CLOSED = 2,
DOOR_DEBUG = 3,
DOOR_NUM_MODES = 4
};
const FString DoorModeNames[DOOR_NUM_MODES] = {"Partially Open", "Open", "Closed", "Debug"};
EDoorMode DoorCurrentMode = DOOR_PARTIALLY_OPEN;
const float DoorOpeningWidthRelative = 0.522; //%, used for the overlay width on the screen
const float DoorOpeningWidthAbsolute = 165; // cm, used for the non tape part at the door
const float WallDistance = 262.5; // cm, distance from center to a wall, *2 = wall width
const float WallCloseDistance = 75; // cm, the distance considered to be too close to the walls
const float WallFadeDistance = 35; // cm, the distance over which the tape is faded
const float WallWarningDistance = 40; // cm, distance on which the tape turns red, measured from wall
float DoorCurrentOpeningWidthAbsolute = 0;
// Helper function to create a mesh component in the constructor
UStaticMeshComponent* CreateMeshComponent(const FName& Name, USceneComponent* Parent);
// Calculates opacity value used for the dynamic materials of the tape and sign. The closer the more opaque.
double CalculateOpacityFromPosition(const FVector& Position) const;
// Check whether the given position is within the door area of the (partially) open door.
bool PositionInDoorOpening(const FVector& Position) const;
// Sets the position, orientation and opacity/visibility of the Sign according to the HandPosition.
void SetSignsForHand(UStaticMeshComponent* Sign, const FVector& HandPosition,
UMaterialInstanceDynamic* HandMaterial) const;
// Only calculate positions and material values when we're fully initialized.
bool bInitialized = false;
// Reference to the currently active pawn that we're tracking positions of.
UPROPERTY()
ARWTHVRPawn* VRPawn;
// Cluster Events
FOnClusterEventJsonListener ClusterEventListenerDelegate;
void HandleClusterEvent(const FDisplayClusterClusterEventJson& Event);
public:
virtual void Tick(float DeltaTime) override;
// Change door mode manually between open, partially open and closed.
void CycleDoorType();
void SetDoorMode(EDoorMode M);
// Root component
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true"))
USceneComponent* Root;
// Tape Static Mesh component. Reference to static mesh needs to be set in the corresponding BP.
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* Tape;
// Right Hand Sign Static Mesh component. Reference to static mesh needs to be set in the corresponding BP.
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* SignRightHand;
// Left Hand Sign Static Mesh component. Reference to static mesh needs to be set in the corresponding BP.
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* SignLeftHand;
// UI Overlay
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "CAVEOverlay")
TSubclassOf<UDoorOverlayData> OverlayClass;
// UI Overlay
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "CAVEOverlay")
UInputAction* CycleDoorTypeInputAction;
UPROPERTY()
UDoorOverlayData* Overlay;
// Dynamic Materials to control opacity
UPROPERTY()
UMaterialInstanceDynamic* TapeMaterialDynamic;
UPROPERTY()
UMaterialInstanceDynamic* RightSignMaterialDynamic;
UPROPERTY()
UMaterialInstanceDynamic* LeftSignMaterialDynamic;
};
#pragma once
#include "CoreMinimal.h"
#include "Engine/DeveloperSettings.h"
#include "CAVEOverlaySettings.generated.h"
UENUM(BlueprintType)
enum DefaultActivationType
{
DefaultActivationType_OFF UMETA(DisplayName = "Off by default"),
DefaultActivationType_ON UMETA(DisplayName = "On by default")
};
UCLASS(config = Game, defaultconfig, meta = (DisplayName = "CAVE Overlay"))
class RWTHVRCLUSTER_API UCAVEOverlaySettings : public UDeveloperSettings
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, config, Category = "General", meta = (DisplayName = "Default Activation Type"))
TEnumAsByte<DefaultActivationType> DefaultActivationType = DefaultActivationType_ON;
UPROPERTY(EditAnywhere, config, Category = Maps, meta = (AllowedClasses = "/Script/Engine.World"))
TArray<FSoftObjectPath> ExcludedMaps;
};
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Components/TextBlock.h"
#include "Components/Border.h"
#include "DoorOverlayData.generated.h"
/**
* Used as a parent-class in the overlay widget. Like this we can access the UMG properties in C++
*/
UCLASS()
class RWTHVRCLUSTER_API UDoorOverlayData : public UUserWidget
{
GENERATED_BODY()
public:
// These declarations are magically bound to the UMG blueprints elements,
// if they are named the same
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
UTextBlock* CornerText;
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
UBorder* BlackBox;
};
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "LiveLinkPreset.h"
#include "GameFramework/Actor.h"
#include "CaveSetup.generated.h"
/**
* Simple Actor that needs to be added to the level which spawns Cave-related actors
* such as the CaveOverlay.
* It attaches itself to the Primary Node's Pawn and then replicates on the server.
*/
UCLASS(hideCategories = (Rendering, Input, Actor, Base, Collision, Shape, Physics, HLOD))
class RWTHVRCLUSTER_API ACaveSetup : public AActor
{
GENERATED_BODY()
public:
ACaveSetup();
UPROPERTY(EditAnywhere)
TArray<UClass*> ActorsToSpawnOnCave;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
ULiveLinkPreset* LiveLinkPresetToApplyOnCave;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
};
#pragma once
#include "CoreMinimal.h"
#include "HAL/IConsoleManager.h"
#include "Cluster/IDisplayClusterClusterManager.h"
#include "ClusterConsole.generated.h"
/**
* The ClusterConsole provides the console command "ClusterExecute"
* The code catches your command, broadcasts it to every nDisplay node and executes it everywhere
*
* This class has to be registered and unregistered. This can easily be done in every StartupModule/ShutdownModule
* functions.
*/
USTRUCT()
struct RWTHVRCLUSTER_API FClusterConsole
{
GENERATED_BODY()
private:
/* Used for ClusterExecute console command */
IConsoleCommand* ClusterConsoleCommand = nullptr;
FOnClusterEventJsonListener ClusterEventListenerDelegate;
public:
void Register();
void Unregister() const;
};
#pragma once
#include "Serialization/MemoryReader.h"
#include "Serialization/MemoryWriter.h"
#include "type_traits"
template <typename ParameterType, typename... RemainingParameterTypes>
inline void SerializeParameters(FMemoryWriter* MemoryWriter, ParameterType&& Parameter,
RemainingParameterTypes&&... RemainingParameters)
{
using NonConstType = typename std::remove_cv_t<typename TRemoveReference<ParameterType>::Type>;
// const_cast because the same operator (<<) is used for reading and writing
(*MemoryWriter) << const_cast<NonConstType&>(Parameter);
SerializeParameters(MemoryWriter, Forward<RemainingParameterTypes>(RemainingParameters)...);
}
inline void SerializeParameters(FMemoryWriter* MemoryWriter) {}
// This is a wrapper function to recursively fill the argument tuple. This overload is only used if the index indicating
// the currently handled attribute is less than the number of total attributes. I.e., if the attribute index is valid.
template <int CurrentIndex, typename... ArgTypes>
inline typename TEnableIf<(CurrentIndex < sizeof...(ArgTypes))>::Type
FillArgumentTuple(FMemoryReader* MemoryReader, TTuple<ArgTypes...>* ArgumentTuple)
{
// Read the "<<" as ">>" operator here. FArchive uses the same for both and decides based on an internal type on
// what to do. So this statement parses the bytes that were passed into reader and puts the parsed object into the
// tuple at index CurrentIndex.
(*MemoryReader) << ArgumentTuple->template Get<CurrentIndex>();
// Recursive call for the remaining attributes.
FillArgumentTuple<CurrentIndex + 1>(MemoryReader, Forward<TTuple<ArgTypes...>*>(ArgumentTuple));
}
// The overload that is called if we are "passed the end" of attributes.
template <int CurrentIndex, typename... ArgTypes>
inline typename TEnableIf<(CurrentIndex >= sizeof...(ArgTypes))>::Type
FillArgumentTuple(FMemoryReader* MemoryReader, TTuple<ArgTypes...>* ArgumentTuple)
{
}
template <typename RetType, typename... ArgTypes>
inline RetType CallDelegateWithParameterMap(const TDelegate<RetType, ArgTypes...>& Delegate,
const TMap<FString, FString>& Parameters)
{
// Create a tuple that holds all arguments. This assumes that all argument types are default constructible. However,
// all types that overload the FArchive "<<" operator probably are.
TTuple<typename std::remove_cv_t<typename TRemoveReference<ArgTypes>::Type>...> ArgumentTuple;
// This call will parse the string map and fill all values in the tuple appropriately.
FillArgumentTuple<0>(&ArgumentTuple, Parameters);
// The lambda function is only necessary because delegates do not overload the ()-operator but use the Execute()
// method instead. So, the lambda acts as a wrapper.
return ArgumentTuple.ApplyBefore([Delegate](ArgTypes&&... Arguments)
{ Delegate.Execute(Forward<ArgTypes>(Arguments)...); });
}
#pragma once
#include "IDisplayCluster.h"
#include "Cluster/IDisplayClusterClusterManager.h"
#include "Cluster/DisplayClusterClusterEvent.h"
#include "DisplayClusterEventParameterHelper.h"
#include "Templates/IsInvocable.h"
static constexpr int32 CLUSTER_EVENT_WRAPPER_EVENT_ID = 1337420;
template <typename MemberFunctionType, MemberFunctionType MemberFunction>
class ClusterEventWrapperEvent;
template <typename ObjectType, typename ReturnType, typename... ArgTypes,
ReturnType (ObjectType::*MemberFunction)(ArgTypes...)>
class ClusterEventWrapperEvent<ReturnType (ObjectType::*)(ArgTypes...), MemberFunction>
{
static_assert(TIsDerivedFrom<ObjectType, UObject>::IsDerived, "Object needs to derive from UObject");
public:
using MemberFunctionType = decltype(MemberFunction);
ClusterEventWrapperEvent(const TCHAR* MethodName) : MethodName{MethodName} {}
void Attach(ObjectType* NewObject)
{
checkf(Object == nullptr, TEXT("The event is already attached."));
Object = NewObject;
ObjectId = Object->GetUniqueID();
EDisplayClusterOperationMode OperationMode = IDisplayCluster::Get().GetOperationMode();
if (OperationMode == EDisplayClusterOperationMode::Cluster)
{
IDisplayClusterClusterManager* ClusterManager = IDisplayCluster::Get().GetClusterMgr();
check(ClusterManager != nullptr);
check(!ClusterEventListenerDelegate.IsBound());
ClusterEventListenerDelegate = FOnClusterEventBinaryListener::CreateLambda(
[this](const FDisplayClusterClusterEventBinary& Event)
{
if (Event.EventId != CLUSTER_EVENT_WRAPPER_EVENT_ID)
{
return;
}
FMemoryReader MemoryReader(Event.EventData);
uint32 EventObjectId;
// This reads the value!
MemoryReader << EventObjectId;
if (EventObjectId != ObjectId)
{
// Event does not belong to this object.
return;
}
FString EventMethodName;
// This reads the value!
MemoryReader << EventMethodName;
if (EventMethodName != MethodName)
{
// This event does not belong to this method.
return;
}
// Create a tuple that holds all arguments. This assumes that all
// argument types are default constructible. However, all
// types that overload the FArchive "<<" operator probably are.
TTuple<typename std::remove_cv_t<typename TRemoveReference<ArgTypes>::Type>...> ArgumentTuple;
// This call will deserialze the values and fill all values in the tuple appropriately.
FillArgumentTuple<0>(&MemoryReader, &ArgumentTuple);
ArgumentTuple.ApplyBefore([this](const ArgTypes&... Arguments)
{ (Object->*MemberFunction)(Forward<const ArgTypes&>(Arguments)...); });
});
ClusterManager->AddClusterEventBinaryListener(ClusterEventListenerDelegate);
}
}
void Detach()
{
checkf(Object != nullptr, TEXT("The event was never attached."));
EDisplayClusterOperationMode OperationMode = IDisplayCluster::Get().GetOperationMode();
if (OperationMode == EDisplayClusterOperationMode::Cluster)
{
IDisplayClusterClusterManager* ClusterManager = IDisplayCluster::Get().GetClusterMgr();
check(ClusterManager != nullptr);
// check(ClusterEventListenerDelegate.IsBound());
ClusterManager->RemoveClusterEventBinaryListener(ClusterEventListenerDelegate);
}
}
void Send(ArgTypes... Arguments)
{
checkf(Object != nullptr, TEXT("The event was not attached."));
EDisplayClusterOperationMode OperationMode = IDisplayCluster::Get().GetOperationMode();
if (OperationMode != EDisplayClusterOperationMode::Cluster)
{
(Object->*MemberFunction)(Forward<ArgTypes>(Arguments)...);
}
else
{
IDisplayClusterClusterManager* ClusterManager = IDisplayCluster::Get().GetClusterMgr();
check(ClusterManager != nullptr);
FDisplayClusterClusterEventBinary ClusterEvent;
ClusterEvent.EventId = CLUSTER_EVENT_WRAPPER_EVENT_ID;
ClusterEvent.bShouldDiscardOnRepeat = false;
FMemoryWriter MemoryWriter(ClusterEvent.EventData);
MemoryWriter << ObjectId;
MemoryWriter << const_cast<FString&>(MethodName);
SerializeParameters(&MemoryWriter, Forward<ArgTypes>(Arguments)...);
ClusterManager->EmitClusterEventBinary(ClusterEvent, true);
}
}
private:
const FString MethodName;
uint32 ObjectId;
ObjectType* Object = nullptr;
FOnClusterEventBinaryListener ClusterEventListenerDelegate;
};
#define DCEW_STRINGIFY(x) #x
#define DCEW_TOSTRING(x) DCEW_STRINGIFY(x)
#define DECLARE_DISPLAY_CLUSTER_EVENT(OwningType, MethodIdentifier) \
ClusterEventWrapperEvent<decltype(&OwningType::MethodIdentifier), &OwningType::MethodIdentifier> \
MethodIdentifier##Event \
{ \
TEXT(DCEW_TOSTRING(OwningType) DCEW_TOSTRING(MethodIdentifier)) \
}
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
#include "ClusterConsole/ClusterConsole.h"
class FRWTHVRClusterModule : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
private:
FClusterConsole ClusterConsole;
};
using UnrealBuildTool;
public class RWTHVRCluster : ModuleRules
{
public RWTHVRCluster(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] { }
);
PrivateIncludePaths.AddRange(
new string[] { }
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"CoreUObject",
"Engine",
"DeveloperSettings",
"EnhancedInput",
"UMG",
"Slate",
"SlateCore",
"LiveLink",
"DisplayCluster",
"RWTHVRToolkit"
}
);
if (IsPluginEnabledForTarget("DTrackPlugin", base.Target))
{
PublicDependencyModuleNames.AddRange(
new string[]
{
"DTrackPlugin",
"DTrackInput"
}
);
}
PrivateDependencyModuleNames.AddRange(
new string[] { }
);
DynamicallyLoadedModuleNames.AddRange(
new string[] { }
);
}
private static bool IsPluginEnabledForTarget(string PluginName, ReadOnlyTargetRules Target)
{
var PL = Plugins.GetPlugin(PluginName);
return PL != null && Target.ProjectFile != null && Plugins.IsPluginEnabledForTarget(PL,
ProjectDescriptor.FromFile(Target.ProjectFile), Target.Platform, Target.Configuration, Target.Type);
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment