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

Merge branch 'refactor/plugin_separation' into 'dev/5.3'

Plugin Separation: Move the RWTHVRCluster Module into its own Plugin with respective content. Removes Toolkit dependency to nDisplay

See merge request !79
parents 53c84a3f 2eb1d91a
Branches
Tags
2 merge requests!85UE5.3-2023.1-rc3,!79Plugin Separation: Move the RWTHVRCluster Module into its own Plugin with respective content. Removes Toolkit dependency to nDisplay
Showing
with 31 additions and 662 deletions
...@@ -80,8 +80,21 @@ Generate_Project: ...@@ -80,8 +80,21 @@ Generate_Project:
variables: variables:
RUN_SETUP: "false" RUN_SETUP: "false"
GEN_DEPENDENCIES: "( GEN_DEPENDENCIES: "(
[master@UnrealDTrackPlugin]='https://github.com/VRGroupRWTH/UnrealDTrackPlugin.git')" [master@UnrealDTrackPlugin]='https://github.com/VRGroupRWTH/UnrealDTrackPlugin.git'
[dev/5.3@RWTHVRCluster]='https://git-ce.rwth-aachen.de/vr-vis/VR-Group/unreal-development/plugins/rwth-vr-cluster-plugin.git'
)"
Generate_Project_Without_Cluster:
extends: Generate_Project
variables:
GEN_DEPENDENCIES: "(
[master@UnrealDTrackPlugin]='https://github.com/VRGroupRWTH/UnrealDTrackPlugin.git'
)"
script: # if we don't do this, cooking will for some reason fail even though it should use the correct fallback
- !reference [Generate_Project, script]
- echo "Generating without cluster mode, removing nDisplay configs..."
- cd ${CI_PROJECT_DIR}
- sed -i '/DisplayCluster/d' Config/Linux/LinuxEngine.ini
Build_Windows: Build_Windows:
rules: rules:
...@@ -124,6 +137,14 @@ Build_Linux: ...@@ -124,6 +137,14 @@ Build_Linux:
- job: "Generate_Project" - job: "Generate_Project"
artifacts: true artifacts: true
# Builds for linux without the cluster plugin
Build_Linux_Without_Cluster:
extends: Build_Linux
needs:
- job: "Generate_Project_Without_Cluster"
artifacts: true
# Deploys to vrdev # Deploys to vrdev
Deploy_Windows: Deploy_Windows:
rules: rules:
......
No preview for this file type
No preview for this file type
File deleted
File deleted
File deleted
File deleted
File deleted
File deleted
File deleted
File deleted
File deleted
File deleted
...@@ -15,15 +15,6 @@ ...@@ -15,15 +15,6 @@
"Installed": false, "Installed": false,
"EnabledByDefault": true, "EnabledByDefault": true,
"Modules": [ "Modules": [
{
"Name": "RWTHVRCluster",
"Type": "Runtime",
"LoadingPhase": "Default",
"WhitelistPlatforms": [
"Win64",
"Linux"
]
},
{ {
"Name": "RWTHVRToolkit", "Name": "RWTHVRToolkit",
"Type": "Runtime", "Type": "Runtime",
...@@ -37,22 +28,18 @@ ...@@ -37,22 +28,18 @@
], ],
"Plugins": [ "Plugins": [
{ {
"Name": "nDisplay", "Name": "RWTHVRCluster",
"Enabled": true "Enabled": true,
"Optional": true,
"SupportedTargetPlatforms": [
"Win64",
"Linux"
]
}, },
{ {
"Name": "LiveLink", "Name": "LiveLink",
"Enabled": true "Enabled": true
}, },
{
"Name": "LiveLinkOvernDisplay",
"Enabled": true
},
{
"Name": "DTrackPlugin",
"Enabled": true,
"Optional": true
},
{ {
"Name": "EnhancedInput", "Name": "EnhancedInput",
"Enabled": true "Enabled": true
...@@ -62,9 +49,8 @@ ...@@ -62,9 +49,8 @@
"Enabled": true "Enabled": true
}, },
{ {
"Name": "Switchboard", "Name": "XRBase",
"Enabled": true, "Enabled": true
"Optional": true
} }
] ]
} }
#include "CAVEOverlay/CAVEOverlayController.h"
#include "CoreMinimal.h"
#include "EnhancedInputComponent.h"
#include "IDisplayCluster.h"
#include "MotionControllerComponent.h"
#include "Camera/CameraComponent.h"
#include "CAVEOverlay/DoorOverlayData.h"
#include "Cluster/DisplayClusterClusterEvent.h"
#include "Cluster/IDisplayClusterClusterManager.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/CollisionProfile.h"
#include "Logging/StructuredLog.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Pawn/RWTHVRPawn.h"
#include "Utility/RWTHVRUtilities.h"
DEFINE_LOG_CATEGORY(LogCAVEOverlay);
// Helper function to check if a string is contained within an array of strings, ignoring case.
bool ContainsFString(const TArray<FString>& Array, const FString& Entry)
{
for (FString Current_Entry : Array)
{
if (Current_Entry.Equals(Entry, ESearchCase::IgnoreCase))
return true;
}
return false;
}
UStaticMeshComponent* ACAVEOverlayController::CreateMeshComponent(const FName& Name, USceneComponent* Parent)
{
UStaticMeshComponent* Result = CreateDefaultSubobject<UStaticMeshComponent>(Name);
Result->SetupAttachment(Parent);
Result->SetVisibility(false);
Result->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
return Result;
}
// Sets default values
ACAVEOverlayController::ACAVEOverlayController()
{
// Set this actor to call Tick() every frame.
PrimaryActorTick.bCanEverTick = true;
bAllowTickBeforeBeginPlay = false;
// Creation of sub-components
Root = CreateDefaultSubobject<USceneComponent>("DefaultSceneRoot");
SetRootComponent(Root);
Tape = CreateMeshComponent("Tape", Root);
SignRightHand = CreateMeshComponent("SignRightHand", Root);
SignLeftHand = CreateMeshComponent("SignLeftHand", Root);
}
void ACAVEOverlayController::CycleDoorType()
{
DoorCurrentMode = static_cast<EDoorMode>((DoorCurrentMode + 1) % DOOR_NUM_MODES);
// Send out a cluster event to the whole cluster that the door mode has been changed
if (auto* const Manager = IDisplayCluster::Get().GetClusterMgr())
{
FDisplayClusterClusterEventJson cluster_event;
cluster_event.Name = "CAVEOverlay Change Door to " + DoorModeNames[DoorCurrentMode];
cluster_event.Type = "DoorChange";
cluster_event.Category = "CAVEOverlay";
cluster_event.Parameters.Add("NewDoorState", FString::FromInt(DoorCurrentMode));
Manager->EmitClusterEventJson(cluster_event, true);
}
}
void ACAVEOverlayController::HandleClusterEvent(const FDisplayClusterClusterEventJson& Event)
{
if (Event.Category.Equals("CAVEOverlay") && Event.Type.Equals("DoorChange") &&
Event.Parameters.Contains("NewDoorState"))
{
SetDoorMode(static_cast<EDoorMode>(FCString::Atoi(*Event.Parameters["NewDoorState"])));
}
}
void ACAVEOverlayController::SetDoorMode(const EDoorMode NewMode)
{
DoorCurrentMode = NewMode;
switch (DoorCurrentMode)
{
case EDoorMode::DOOR_DEBUG:
case EDoorMode::DOOR_PARTIALLY_OPEN:
DoorCurrentOpeningWidthAbsolute = DoorOpeningWidthAbsolute;
if (ScreenType == SCREEN_DOOR)
Overlay->BlackBox->SetRenderScale(FVector2D(0, 1));
if (ScreenType == SCREEN_DOOR_PARTIAL)
Overlay->BlackBox->SetRenderScale(FVector2D(DoorOpeningWidthRelative, 1));
if (ScreenType == SCREEN_PRIMARY)
Overlay->BlackBox->SetRenderScale(FVector2D(0, 1));
Overlay->BlackBox->SetVisibility(ESlateVisibility::Visible);
break;
case EDoorMode::DOOR_OPEN:
DoorCurrentOpeningWidthAbsolute = WallDistance * 2;
if (ScreenType == SCREEN_DOOR)
Overlay->BlackBox->SetRenderScale(FVector2D(1, 1));
if (ScreenType == SCREEN_DOOR_PARTIAL)
Overlay->BlackBox->SetRenderScale(FVector2D(1, 1));
if (ScreenType == SCREEN_PRIMARY)
Overlay->BlackBox->SetRenderScale(FVector2D(0, 1));
Overlay->BlackBox->SetVisibility(ESlateVisibility::Visible);
break;
case EDoorMode::DOOR_CLOSED:
DoorCurrentOpeningWidthAbsolute = 0;
if (ScreenType == SCREEN_DOOR)
Overlay->BlackBox->SetRenderScale(FVector2D(0, 1));
if (ScreenType == SCREEN_DOOR_PARTIAL)
Overlay->BlackBox->SetRenderScale(FVector2D(0, 1));
if (ScreenType == SCREEN_PRIMARY)
Overlay->BlackBox->SetRenderScale(FVector2D(0, 1));
Overlay->BlackBox->SetVisibility(ESlateVisibility::Hidden);
break;
default:;
}
// On the secondary nodes that are not the door, hide the overlay completely
// It might make more sense to just not add it there...
if (ScreenType == SCREEN_NORMAL)
Overlay->BlackBox->SetRenderScale(FVector2D(0, 1));
UE_LOGFMT(LogCAVEOverlay, Log, "Switched door state to {State}. New opening width is {Width}.",
*DoorModeNames[DoorCurrentMode], DoorCurrentOpeningWidthAbsolute);
// On the primary node, show which door mode is currently active.
if (ScreenType == SCREEN_PRIMARY)
{
Overlay->CornerText->SetText(FText::FromString(DoorModeNames[DoorCurrentMode]));
}
}
// Called when the game starts or when spawned
void ACAVEOverlayController::BeginPlay()
{
Super::BeginPlay();
// Don't do anything if we're a dedicated server. We shouldn't even exist there.
if (GetNetMode() == NM_DedicatedServer)
return;
// Currently, there is no support for multi-user systems in general, as we only depend on the local pawn.
// In a MU setting, the relevant pawn isn't our local one, but the primary node's pawn.
if (GetNetMode() != NM_Standalone)
return;
// This should return the respective client's local playercontroller or, if we're a listen server, our own PC.
auto* PC = GetWorld() ? GetWorld()->GetFirstPlayerController() : nullptr;
// it can happen that the PC is valid, but we have no player attached to it yet.
// Check for this - however, we should work around it by somehow getting notified when that happens.
// Not sure which place would be best...
const bool bValidPC = PC && PC->GetLocalPlayer();
if (!bValidPC || !URWTHVRUtilities::IsRoomMountedMode())
return;
// Input config
if (URWTHVRUtilities::IsPrimaryNode())
{
if (CycleDoorTypeInputAction == nullptr)
{
UE_LOGFMT(LogCAVEOverlay, Error, "Input action and mapping not set in CaveOverlayController!");
return;
}
UEnhancedInputComponent* Input = Cast<UEnhancedInputComponent>(PC->InputComponent);
Input->BindAction(CycleDoorTypeInputAction, ETriggerEvent::Triggered, this,
&ACAVEOverlayController::CycleDoorType);
}
// Bind the cluster events that manage the door state.
IDisplayClusterClusterManager* ClusterManager = IDisplayCluster::Get().GetClusterMgr();
if (ClusterManager && !ClusterEventListenerDelegate.IsBound())
{
ClusterEventListenerDelegate =
FOnClusterEventJsonListener::CreateUObject(this, &ACAVEOverlayController::HandleClusterEvent);
ClusterManager->AddClusterEventJsonListener(ClusterEventListenerDelegate);
}
// Determine the screen-type for later usage
if (IDisplayCluster::Get().GetClusterMgr()->GetNodeId().Equals(ScreenMain, ESearchCase::IgnoreCase))
{
ScreenType = SCREEN_PRIMARY;
}
else if (ContainsFString(ScreensDoor, IDisplayCluster::Get().GetClusterMgr()->GetNodeId()))
{
ScreenType = SCREEN_DOOR;
}
else if (ContainsFString(ScreensDoorPartial, IDisplayCluster::Get().GetClusterMgr()->GetNodeId()))
{
ScreenType = SCREEN_DOOR_PARTIAL;
}
else
{
ScreenType = SCREEN_NORMAL;
}
// Create and add widget to local playercontroller.
if (!OverlayClass)
{
UE_LOGFMT(LogCAVEOverlay, Error, "OverlayClass not set in CaveOverlayController!");
return;
}
Overlay = CreateWidget<UDoorOverlayData>(PC, OverlayClass);
Overlay->AddToViewport(0);
// Set the default door mode (partially open)
SetDoorMode(DoorCurrentMode);
// Set Text to "" until someone presses the key for the first time
Overlay->CornerText->SetText(FText::FromString(""));
// Get the pawn so we can have access to head and hand positions
VRPawn = Cast<ARWTHVRPawn>(PC->GetPawnOrSpectator());
if (VRPawn)
{
// we're good to go!
bInitialized = true;
}
else
{
UE_LOGFMT(LogCAVEOverlay, Error, "No VirtualRealityPawn found which we could attach to!");
}
// Create dynamic materials at runtime
TapeMaterialDynamic = Tape->CreateDynamicMaterialInstance(0);
RightSignMaterialDynamic = SignRightHand->CreateDynamicMaterialInstance(0);
LeftSignMaterialDynamic = SignLeftHand->CreateDynamicMaterialInstance(0);
UE_LOGFMT(LogCAVEOverlay, Display, "CaveOverlay Initialization was successfull.");
}
void ACAVEOverlayController::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
IDisplayClusterClusterManager* ClusterManager = IDisplayCluster::Get().GetClusterMgr();
if (ClusterManager && ClusterEventListenerDelegate.IsBound())
{
ClusterManager->RemoveClusterEventJsonListener(ClusterEventListenerDelegate);
}
Super::EndPlay(EndPlayReason);
}
double ACAVEOverlayController::CalculateOpacityFromPosition(const FVector& Position) const
{
// Calculate opacity value depending on how far we are from the walls. Further away == lower opacity,
// fully opaque when WallFadeDistance away from the wall. Could just use a lerp here..
return FMath::Max(
FMath::Clamp((FMath::Abs(Position.X) - (WallDistance - WallCloseDistance)) / WallFadeDistance, 0.0, 1.0),
FMath::Clamp((FMath::Abs(Position.Y) - (WallDistance - WallCloseDistance)) / WallFadeDistance, 0.0, 1.0));
}
bool ACAVEOverlayController::PositionInDoorOpening(const FVector& Position) const
{
// The position of the corner with 10cm of buffer. In negative direction because the door is in negative direction
// of the cave
const float CornerValue = -(WallDistance + 10);
// Check whether our X position is within the door zone. This zone starts 10cm further away from the wall
// than the WallCloseDistance, and ends 10cm outside of the wall (door). As the door is in negative X direction,
// the signs need to be negated.
const bool bXWithinDoor =
FMath::IsWithinInclusive(Position.X, CornerValue, -(WallDistance - WallCloseDistance - 10));
// Checks whether our Y position is between the lower corner with some overlap and
// the door corner (CornerValue + DoorCurrentOpeningWidthAbsolute)
const bool bYWithinDoor =
FMath::IsWithinInclusive(Position.Y, CornerValue, CornerValue + DoorCurrentOpeningWidthAbsolute);
return bXWithinDoor && bYWithinDoor;
}
void ACAVEOverlayController::SetSignsForHand(UStaticMeshComponent* Sign, const FVector& HandPosition,
UMaterialInstanceDynamic* HandMaterial) const
{
const bool bHandIsCloseToWall =
FMath::IsWithinInclusive(HandPosition.GetAbsMax(), WallDistance - WallCloseDistance, WallDistance);
if (bHandIsCloseToWall && !PositionInDoorOpening(HandPosition))
{
Sign->SetVisibility(true);
HandMaterial->SetScalarParameterValue("SignOpacity", CalculateOpacityFromPosition(HandPosition));
// Which wall are we closest to? This is the wall we project the sign onto
const bool bXWallCloser = FMath::Abs(HandPosition.X) > FMath::Abs(HandPosition.Y);
// Set the position towards the closest wall to the wall itself, keep the other positions
const double X = bXWallCloser ? FMath::Sign(HandPosition.X) * WallDistance : HandPosition.X;
const double Y = bXWallCloser ? HandPosition.Y : FMath::Sign(HandPosition.Y) * WallDistance;
const double Z = HandPosition.Z;
// Rotate the sign by 90° if we're on a side wall
const auto Rot = bXWallCloser ? FRotator(0, 0, 0) : FRotator(0, 90, 0);
const auto Pos = FVector(X, Y, Z);
Sign->SetRelativeLocationAndRotation(Pos, Rot);
}
else
{
Sign->SetVisibility(false);
}
}
void ACAVEOverlayController::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// If we're not yet initialized, do nothing. This shouldn't really happen as we only spawn on the cave anyway
if (!bInitialized)
{
return;
}
// Head/Tape Logic
const FVector HeadPosition = VRPawn->HeadCameraComponent->GetRelativeTransform().GetLocation();
const bool bHeadIsCloseToWall =
FMath::IsWithinInclusive(HeadPosition.GetAbsMax(), WallDistance - WallCloseDistance, WallDistance);
// Only show the tape when close to a wall and not within the door opening
if (bHeadIsCloseToWall && !PositionInDoorOpening(HeadPosition))
{
Tape->SetVisibility(true);
// Offset the tape in z direction to always be at head height
Tape->SetRelativeLocation(HeadPosition * FVector(0, 0, 1));
TapeMaterialDynamic->SetScalarParameterValue("BarrierOpacity", CalculateOpacityFromPosition(HeadPosition));
if (FMath::IsWithin(FVector2D(HeadPosition).GetAbsMax(), WallDistance - WallWarningDistance, WallDistance))
{
// in warning distance == red tape
TapeMaterialDynamic->SetVectorParameterValue("StripeColor", FVector(1, 0, 0));
}
else
{
// otherwise we're just yellow
TapeMaterialDynamic->SetVectorParameterValue("StripeColor", FVector(1, 1, 0));
}
}
else
{
Tape->SetVisibility(false);
}
// Hand Logic
const FVector RightPosition = VRPawn->RightHand->GetRelativeTransform().GetLocation();
const FVector LeftPosition = VRPawn->LeftHand->GetRelativeTransform().GetLocation();
// Set the position rotation, opacity, visibility of the hand warning signs.
SetSignsForHand(SignRightHand, RightPosition, RightSignMaterialDynamic);
SetSignsForHand(SignLeftHand, LeftPosition, LeftSignMaterialDynamic);
}
// 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 "GameFramework/Actor.h"
#include "CAVEOverlay/DoorOverlayData.h"
#include "Cluster/IDisplayClusterClusterManager.h"
#include "Pawn/RWTHVRPawn.h"
#include "CAVEOverlayController.generated.h"
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;
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment