diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..1b545cb627f9b82d7fc3edba6aa6998457847ba0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,72 @@
+# Visual Studio 2015 user specific files
+.vs/
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+*.ipa
+
+# These project files can be generated by the engine
+*.xcodeproj
+*.xcworkspace
+*.sln
+*.suo
+*.opensdf
+*.sdf
+*.VC.db
+*.VC.opendb
+
+# Precompiled Assets
+SourceArt/**/*.png
+SourceArt/**/*.tga
+
+# Binary Files
+Binaries/*
+Plugins/*/Binaries/*
+
+# Builds
+Build/*
+
+# Whitelist PakBlacklist-<BuildConfiguration>.txt files
+!Build/*/
+Build/*/**
+!Build/*/PakBlacklist*.txt
+
+# Don't ignore icon files in Build
+!Build/**/*.ico
+
+# Configuration files generated by the Editor
+Saved/*
+
+# Compiled source files for the engine to use
+Intermediate/*
+Plugins/*/Intermediate/*
+
+# Cache files for the editor to use
+DerivedDataCache/*
+LocalDerivedDataCache/*
\ No newline at end of file
diff --git a/CAVEOverlay.uplugin b/CAVEOverlay.uplugin
new file mode 100644
index 0000000000000000000000000000000000000000..63a2f00daedbadd17666a869f285b3cb906ecf0c
--- /dev/null
+++ b/CAVEOverlay.uplugin
@@ -0,0 +1,35 @@
+{
+	"FileVersion": 3,
+	"Version": 1,
+	"VersionName": "1.0",
+	"FriendlyName": "CAVEOverlay",
+	"Description": "Adds the ability to black out the CAVE door and display the magic tape",
+	"Category": "Other",
+	"CreatedBy": "",
+	"CreatedByURL": "",
+	"DocsURL": "",
+	"MarketplaceURL": "",
+	"SupportURL": "",
+	"CanContainContent": true,
+	"IsBetaVersion": false,
+	"Installed": false,
+	"Modules": [
+		{
+			"Name": "CAVEOverlay",
+			"Type": "Runtime",
+			"LoadingPhase": "Default"
+		}
+	],
+   "Plugins": [
+		{
+			"Name": "nDisplay",
+			"Enabled": true
+		}
+   ],
+   "Plugins": [
+		{
+			"Name": "nDisplayExtensions",
+			"Enabled": true
+		}
+   ]
+}
\ No newline at end of file
diff --git a/Content/DoorOverlay.uasset b/Content/DoorOverlay.uasset
new file mode 100644
index 0000000000000000000000000000000000000000..ccf451ce839f78c61f120fda328a0b2a3e7779da
Binary files /dev/null and b/Content/DoorOverlay.uasset differ
diff --git a/Content/Plane.uasset b/Content/Plane.uasset
new file mode 100644
index 0000000000000000000000000000000000000000..bcf108519c2d474416a7ccbbd5dedff4c3f967a3
Binary files /dev/null and b/Content/Plane.uasset differ
diff --git a/Content/StopMaterial.uasset b/Content/StopMaterial.uasset
new file mode 100644
index 0000000000000000000000000000000000000000..72b118af9330aac36c29d3fa8dcd39ae33a3cf74
Binary files /dev/null and b/Content/StopMaterial.uasset differ
diff --git a/Content/StopSign.uasset b/Content/StopSign.uasset
new file mode 100644
index 0000000000000000000000000000000000000000..567392a66464eb2d06a3ea10d198193f4f94d46b
Binary files /dev/null and b/Content/StopSign.uasset differ
diff --git a/Content/Stripes.uasset b/Content/Stripes.uasset
new file mode 100644
index 0000000000000000000000000000000000000000..12ef411c89bc19b928f1478b336f4738800abd62
Binary files /dev/null and b/Content/Stripes.uasset differ
diff --git a/Resources/Icon128.png b/Resources/Icon128.png
new file mode 100644
index 0000000000000000000000000000000000000000..cf4bc6653dabb786395c87c375817c682d1997bf
Binary files /dev/null and b/Resources/Icon128.png differ
diff --git a/Source/CAVEOverlay/CAVEOverlay.Build.cs b/Source/CAVEOverlay/CAVEOverlay.Build.cs
new file mode 100644
index 0000000000000000000000000000000000000000..40cb1d85fd006b83741d63f16f23fdd4e1ef483d
--- /dev/null
+++ b/Source/CAVEOverlay/CAVEOverlay.Build.cs
@@ -0,0 +1,57 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+using UnrealBuildTool;
+
+public class CAVEOverlay : ModuleRules
+{
+	public CAVEOverlay(ReadOnlyTargetRules Target) : base(Target)
+	{
+		PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
+		
+		PublicIncludePaths.AddRange(
+			new string[] {
+				// ... add public include paths required here ...
+			}
+			);
+				
+		
+		PrivateIncludePaths.AddRange(
+			new string[] {
+				// ... add other private include paths required here ...
+			}
+			);
+			
+		
+		PublicDependencyModuleNames.AddRange(
+			new string[]
+			{
+				"Core",
+				"UMG",
+				"DisplayCluster",
+				"DisplayClusterExtensions",
+				"InputCore",
+				"CoreUObject",
+				"Engine",
+				"Slate",
+				"SlateCore"
+				// ... add other public dependencies that you statically link with here ...
+			}
+			);
+			
+		
+		PrivateDependencyModuleNames.AddRange(
+			new string[]
+			{
+				// ... add private dependencies that you statically link with here ...	
+			}
+			);
+		
+		
+		DynamicallyLoadedModuleNames.AddRange(
+			new string[]
+			{
+				// ... add any modules that your module loads dynamically here ...
+			}
+			);
+	}
+}
diff --git a/Source/CAVEOverlay/Private/CAVEOverlay.cpp b/Source/CAVEOverlay/Private/CAVEOverlay.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a980703080b4df9a46a191adffe60e4891886c1f
--- /dev/null
+++ b/Source/CAVEOverlay/Private/CAVEOverlay.cpp
@@ -0,0 +1,20 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#include "CAVEOverlay.h"
+
+#define LOCTEXT_NAMESPACE "FCAVEOverlayModule"
+
+void FCAVEOverlayModule::StartupModule()
+{
+	// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
+}
+
+void FCAVEOverlayModule::ShutdownModule()
+{
+	// This function may be called during shutdown to clean up your module.  For modules that support dynamic reloading,
+	// we call this function before unloading the module.
+}
+
+#undef LOCTEXT_NAMESPACE
+	
+IMPLEMENT_MODULE(FCAVEOverlayModule, CAVEOverlay)
\ No newline at end of file
diff --git a/Source/CAVEOverlay/Private/CAVEOverlayController.cpp b/Source/CAVEOverlay/Private/CAVEOverlayController.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d15ec0f6d11f08e68a9b95abcbea94be4f47b7f6
--- /dev/null
+++ b/Source/CAVEOverlay/Private/CAVEOverlayController.cpp
@@ -0,0 +1,333 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#include "CAVEOverlayController.h"
+#include "CoreMinimal.h"
+#include "GameFramework/Actor.h"
+#include "DoorOverlayData.h"
+#include "IDisplayCluster.h"
+#include "Engine/Engine.h"
+#include "IXRTrackingSystem.h"
+#include "Components/InputComponent.h"
+#include "Cluster/IDisplayClusterClusterManager.h"
+#include "IDisplayClusterGameManager.h"
+#include "UObject/ConstructorHelpers.h"
+#include <array>
+#include "Components/StaticMeshComponent.h"
+#include "Materials/Material.h"
+#include "Materials/MaterialInstanceDynamic.h"
+#include "DisplayClusterExtensions/Public/VirtualRealityPawn.h"
+#include "DisplayCluster/Public/DisplayClusterSceneComponent.h"
+
+DEFINE_LOG_CATEGORY(LogCAVEOverlay);
+
+template <std::size_t S>
+bool ContainsFString(const std::array<FString, S>& 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, UStaticMesh* Mesh, USceneComponent* Parent)
+{
+	UStaticMeshComponent* Result = CreateDefaultSubobject<UStaticMeshComponent>(Name);
+	Result->SetStaticMesh(Mesh);
+	Result->SetupAttachment(Parent);
+	Result->SetVisibility(false);
+	return Result;
+}
+
+template <typename T>
+bool LoadAsset(const FString& Path, T* & Result)
+{
+	ConstructorHelpers::FObjectFinder<T> Loader(*Path);
+	Result = Loader.Object;
+	if (!Loader.Succeeded()) UE_LOG(LogCAVEOverlay, Error, TEXT("Could not find %s. Have you renamed it?"), *Path);
+	return Loader.Succeeded();
+}
+
+// Sets default values
+ACAVEOverlayController::ACAVEOverlayController()
+{
+	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
+	PrimaryActorTick.bCanEverTick = true;
+	bAllowTickBeforeBeginPlay = false;
+	AutoReceiveInput = EAutoReceiveInput::Player0;
+
+	ConstructorHelpers::FClassFinder<UDoorOverlayData> WidgetClassFinder(TEXT("Blueprint'/CAVEOverlay/DoorOverlay'"));
+	if (WidgetClassFinder.Succeeded())
+	{
+		Overlay_Class = WidgetClassFinder.Class;
+	}
+	else
+	{
+		UE_LOG(LogCAVEOverlay, Error, TEXT("Could not find the DoorOverlay class. Have you renamed it?"));
+	}
+
+	//Creation of sub-components
+	Root = CreateDefaultSubobject<USceneComponent>("DefaultSceneRoot");
+	SetRootComponent(Root);
+	Tape_Root = CreateDefaultSubobject<USceneComponent>("TapeRoot");
+	Sign_Root = CreateDefaultSubobject<USceneComponent>("SignRoot");
+	Tape_Root->SetupAttachment(Root);
+	Sign_Root->SetupAttachment(Root);
+
+	//Loading of Materials and Meshes
+	LoadAsset("/CAVEOverlay/Stripes", Tape_Material);
+	LoadAsset("/CAVEOverlay/StopMaterial", Sign_Material);
+	LoadAsset("/CAVEOverlay/Plane", Plane_Mesh_);
+
+	Tape_Negative_Y = CreateMeshComponent("TapeNegY", Plane_Mesh_, Tape_Root);
+	Tape_Negative_X = CreateMeshComponent("TapeNegX", Plane_Mesh_, Tape_Root);
+	Tape_Positive_Y = CreateMeshComponent("TapePosY", Plane_Mesh_, Tape_Root);
+	Tape_Positive_X = CreateMeshComponent("TapePosX", Plane_Mesh_, Tape_Root);
+
+	Sign_Negative_Y = CreateMeshComponent("SignNegY", Plane_Mesh_, Sign_Root);
+	Sign_Negative_X = CreateMeshComponent("SignNegX", Plane_Mesh_, Sign_Root);
+	Sign_Positive_Y = CreateMeshComponent("SignPosY", Plane_Mesh_, Sign_Root);
+	Sign_Positive_X = CreateMeshComponent("SignPosX", Plane_Mesh_, Sign_Root);
+
+	//Set initial Position, Rotation and Scale of Tape
+	Tape_Negative_Y->SetRelativeLocationAndRotation(FVector(0, -Wall_Distance, 0), FRotator(0, 0, 90));
+	Tape_Positive_Y->SetRelativeLocationAndRotation(FVector(0, +Wall_Distance, 0), FRotator(0, 180, 90));
+	Tape_Negative_X->SetRelativeLocationAndRotation(FVector(-Wall_Distance, 0, 0), FRotator(0, -90, 90));
+	Tape_Positive_X->SetRelativeLocationAndRotation(FVector(+Wall_Distance, 0, 0), FRotator(0, 90, 90));
+
+	Tape_Negative_Y->SetRelativeScale3D(FVector(Wall_Distance / 100 * 2, 0.15, 1));
+	Tape_Positive_Y->SetRelativeScale3D(FVector(Wall_Distance / 100 * 2, 0.15, 1));
+	Tape_Negative_X->SetRelativeScale3D(FVector(Wall_Distance / 100 * 2, 0.15, 1));
+	Tape_Positive_X->SetRelativeScale3D(FVector(Wall_Distance / 100 * 2, 0.15, 1));
+
+	//Set initial Position, Rotation and Scale of Signs
+	Sign_Negative_Y->SetRelativeLocationAndRotation(FVector(0, -Wall_Distance, 0), FRotator(0, 0, 90));
+	Sign_Positive_Y->SetRelativeLocationAndRotation(FVector(0, +Wall_Distance, 0), FRotator(0, 180, 90));
+	Sign_Negative_X->SetRelativeLocationAndRotation(FVector(-Wall_Distance, 0, 0), FRotator(0, -90, 90));
+	Sign_Positive_X->SetRelativeLocationAndRotation(FVector(+Wall_Distance, 0, 0), FRotator(0, 90, 90));
+
+	Sign_Negative_Y->SetRelativeScale3D(FVector(0.5f));
+	Sign_Positive_Y->SetRelativeScale3D(FVector(0.5f));
+	Sign_Negative_X->SetRelativeScale3D(FVector(0.5f));
+	Sign_Positive_X->SetRelativeScale3D(FVector(0.5f));
+}
+
+void ACAVEOverlayController::PostInitializeComponents()
+{
+	Super::PostInitializeComponents();
+
+	//Create dynamic materials in runtime
+	Tape_Material_Dynamic_ = UMaterialInstanceDynamic::Create(Tape_Material, Tape_Root);
+	Sign_Material_Dynamic_ = UMaterialInstanceDynamic::Create(Sign_Material, Sign_Root);
+
+	Tape_Negative_Y->SetMaterial(0, Tape_Material_Dynamic_);
+	Tape_Negative_X->SetMaterial(0, Tape_Material_Dynamic_);
+	Tape_Positive_Y->SetMaterial(0, Tape_Material_Dynamic_);
+	Tape_Positive_X->SetMaterial(0, Tape_Material_Dynamic_);
+
+	Sign_Negative_Y->SetMaterial(0, Sign_Material_Dynamic_);
+	Sign_Negative_X->SetMaterial(0, Sign_Material_Dynamic_);
+	Sign_Positive_Y->SetMaterial(0, Sign_Material_Dynamic_);
+	Sign_Positive_X->SetMaterial(0, Sign_Material_Dynamic_);
+}
+
+void ACAVEOverlayController::CycleDoorType()
+{
+	Door_Current_Mode = static_cast<EDoor_Mode>((Door_Current_Mode + 1) % DOOR_NUM_MODES);
+	SetDoorMode(Door_Current_Mode);
+}
+
+void ACAVEOverlayController::SetDoorMode(EDoor_Mode M)
+{
+	switch (Door_Current_Mode)
+	{
+	case EDoor_Mode::DOOR_DEBUG:
+	case EDoor_Mode::DOOR_PARTIALLY_OPEN:
+		Door_Current_Opening_Width_Absolute = Door_Opening_Width_Absolute;
+		if (Screen_Type == SCREEN_DOOR) Overlay->BlackBox->SetRenderScale(FVector2D(0, 1));
+		if (Screen_Type == SCREEN_DOOR_PARTIAL) Overlay->BlackBox->SetRenderScale(FVector2D(Door_Opening_Width_Relative, 1));
+		if (Screen_Type == SCREEN_MASTER) Overlay->BlackBox->SetRenderScale(FVector2D(0, 1));
+		Overlay->BlackBox->SetVisibility(ESlateVisibility::Visible);
+		break;
+	case EDoor_Mode::DOOR_OPEN:
+		Door_Current_Opening_Width_Absolute = Wall_Distance * 2;
+		if (Screen_Type == SCREEN_DOOR) Overlay->BlackBox->SetRenderScale(FVector2D(1, 1));
+		if (Screen_Type == SCREEN_DOOR_PARTIAL) Overlay->BlackBox->SetRenderScale(FVector2D(1, 1));
+		if (Screen_Type == SCREEN_MASTER) Overlay->BlackBox->SetRenderScale(FVector2D(1, 1));
+		Overlay->BlackBox->SetVisibility(ESlateVisibility::Visible);
+		break;
+	case EDoor_Mode::DOOR_CLOSED:
+		Door_Current_Opening_Width_Absolute = 0;
+		if (Screen_Type == SCREEN_DOOR) Overlay->BlackBox->SetRenderScale(FVector2D(0, 1));
+		if (Screen_Type == SCREEN_DOOR_PARTIAL) Overlay->BlackBox->SetRenderScale(FVector2D(0, 1));
+		if (Screen_Type == SCREEN_MASTER) Overlay->BlackBox->SetRenderScale(FVector2D(0, 1));
+		Overlay->BlackBox->SetVisibility(ESlateVisibility::Hidden);
+		break;
+	default: ;
+	}
+	if (Screen_Type == SCREEN_NORMAL) Overlay->BlackBox->SetRenderScale(FVector2D(0, 1)); //no overlay
+
+	UE_LOG(LogCAVEOverlay, Log, TEXT("Switched door state to '%s'. New opening width is %f."), *Door_Mode_Names[Door_Current_Mode], Door_Current_Opening_Width_Absolute);
+
+	if (Screen_Type == SCREEN_MASTER)
+	{
+		Overlay->CornerText->SetText(FText::FromString(Door_Mode_Names[Door_Current_Mode]));
+	}
+}
+
+// Called when the game starts or when spawned
+void ACAVEOverlayController::BeginPlay()
+{
+	Super::BeginPlay();
+
+	//Read situation
+	bHMD_Mode = GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowed();
+	bDisplay_Cluster_Mode = IDisplayCluster::Get().GetOperationMode() == EDisplayClusterOperationMode::Cluster;
+
+	if (!bDisplay_Cluster_Mode) return; // Not our business
+
+	//Input config
+	InputComponent->BindAction("Action4", EInputEvent::IE_Pressed, this, &ACAVEOverlayController::CycleDoorType);
+	InputComponent->BindKey(EKeys::F10, EInputEvent::IE_Pressed, this, &ACAVEOverlayController::CycleDoorType);
+
+	//Determine the screen-type for later usage
+	if (IDisplayCluster::Get().GetClusterMgr()->GetNodeId().Equals(Screen_Main, ESearchCase::IgnoreCase))
+	{
+		Screen_Type = SCREEN_MASTER;
+	}
+	else if (ContainsFString(Screens_Door, IDisplayCluster::Get().GetClusterMgr()->GetNodeId()))
+	{
+		Screen_Type = SCREEN_DOOR;
+	}
+	else if (ContainsFString(Screens_Door_Partial, IDisplayCluster::Get().GetClusterMgr()->GetNodeId()))
+	{
+		Screen_Type = SCREEN_DOOR_PARTIAL;
+	}
+	else
+	{
+		Screen_Type = SCREEN_NORMAL;
+	}
+
+	Overlay = CreateWidget<UDoorOverlayData>(GetWorld()->GetFirstPlayerController(), Overlay_Class);
+	Overlay->AddToViewport(0);
+	SetDoorMode(Door_Current_Mode);
+	Overlay->CornerText->SetText(FText::FromString("")); //Set Text to "" until someone presses the key for the first time
+
+	Player_Pawn = Cast<AVirtualRealityPawn>(GetWorld()->GetFirstPlayerController()->GetPawn());
+	RefreshPawnComponents();
+
+	if (!bAttached && Cave_Origin)
+	{
+		AttachToComponent(Cave_Origin, FAttachmentTransformRules::SnapToTargetNotIncludingScale);
+		bAttached = true;
+	}
+}
+
+float ACAVEOverlayController::CalculateOpacityFromPosition(FVector Position)
+{
+	return FMath::Max(
+		FMath::Clamp((FMath::Abs(Position.X) - (Wall_Distance - Wall_Close_Distance)) / Wall_Fade_Distance, 0.0f, 1.0f),
+		FMath::Clamp((FMath::Abs(Position.Y) - (Wall_Distance - Wall_Close_Distance)) / Wall_Fade_Distance, 0.0f, 1.0f)
+	);
+}
+
+bool ACAVEOverlayController::PositionInDoorOpening(FVector Position)
+{
+	return FMath::IsWithinInclusive(-Position.X, Wall_Distance + 10 - 20 - Wall_Close_Distance, Wall_Distance + 10) //Overlap both sides 10cm
+		&& FMath::IsWithinInclusive(-Position.Y, Wall_Distance + 10 - Door_Current_Opening_Width_Absolute, Wall_Distance + 10); //Overlap one side 10cm
+}
+
+void ACAVEOverlayController::RefreshPawnComponents()
+{
+	TArray<UDisplayClusterSceneComponent*> Pawn_Components;
+	Player_Pawn->GetComponents<UDisplayClusterSceneComponent>(Pawn_Components);
+	for (UDisplayClusterSceneComponent* c : Pawn_Components)
+	{
+		if (c->GetName().Equals("cave_origin", ESearchCase::IgnoreCase)) Cave_Origin = c;
+		if (c->GetName().Equals("shutter_glasses", ESearchCase::IgnoreCase)) Shutter_Glasses = c;
+	}
+}
+
+// Called every frame
+void ACAVEOverlayController::Tick(float DeltaTime)
+{
+	Super::Tick(DeltaTime);
+
+	if (!bDisplay_Cluster_Mode) return; // Not our business
+
+	RefreshPawnComponents();
+	if (!bAttached && Cave_Origin)
+	{
+		AttachToComponent(Cave_Origin, FAttachmentTransformRules::SnapToTargetNotIncludingScale);
+		bAttached = true;
+	}
+
+	//FPS Counter
+	if (Overlay) {
+		if (Door_Current_Mode == EDoor_Mode::DOOR_DEBUG && ContainsFString(Screens_FPS, IDisplayCluster::Get().GetClusterMgr()->GetNodeId()))
+		{
+			Overlay->FPS->SetText(FText::FromString(FString::Printf(TEXT("FPS: %.1f"), 1.0f / DeltaTime)));
+		}
+		else
+		{
+			Overlay->FPS->SetText(FText::FromString(""));
+		}
+	}
+	
+	if (!Shutter_Glasses) return; //Display Cluster not initialized
+
+	//Tape Logic
+	FVector Shutter_Position = Shutter_Glasses->GetRelativeTransform().GetLocation();
+	bool bOverlay_Visible = FMath::IsWithinInclusive(Shutter_Position.GetAbsMax(), Wall_Distance - Wall_Close_Distance, Wall_Distance);
+
+	if (bOverlay_Visible && !PositionInDoorOpening(Shutter_Position))
+	{
+		Tape_Root->SetVisibility(true, true);
+		Tape_Root->SetRelativeLocation(Shutter_Position * FVector(0, 0, 1)); //Only apply Z
+
+		float Tape_Opacity = CalculateOpacityFromPosition(Shutter_Position);
+		Tape_Material_Dynamic_->SetScalarParameterValue("BarrierOpacity", Tape_Opacity);
+
+		if (FMath::IsWithin(FVector2D(Shutter_Position).GetAbsMax(), Wall_Distance - Wall_Warning_Distance, Wall_Distance))
+		{
+			//in warning distance == red tape
+			Tape_Material_Dynamic_->SetVectorParameterValue("StripeColor", FVector(1, 0, 0));
+		}
+		else
+		{
+			Tape_Material_Dynamic_->SetVectorParameterValue("StripeColor", FVector(1, 1, 0));
+		}
+	}
+	else
+	{
+		Tape_Root->SetVisibility(false, true);
+	}
+
+	//Sign Logic
+	UDisplayClusterSceneComponent* Flystick = IDisplayCluster::Get().GetGameMgr()->GetNodeById(TEXT("flystick"));
+	if (Flystick)
+	{
+		FVector Flystick_Position = Flystick->GetRelativeTransform().GetLocation();
+		bool bFlystick_In_Door = PositionInDoorOpening(Flystick_Position);
+		float Sign_Opacity = CalculateOpacityFromPosition(Flystick_Position);
+
+		Sign_Negative_X->SetRelativeLocation(FVector(-Wall_Distance, Flystick_Position.Y, Flystick_Position.Z));
+		Sign_Negative_Y->SetRelativeLocation(FVector(Flystick_Position.X, -Wall_Distance, Flystick_Position.Z));
+		Sign_Positive_X->SetRelativeLocation(FVector(+Wall_Distance, Flystick_Position.Y, Flystick_Position.Z));
+		Sign_Positive_Y->SetRelativeLocation(FVector(Flystick_Position.X, +Wall_Distance, Flystick_Position.Z));
+
+		Sign_Negative_X->SetVisibility(FMath::IsWithin(-Flystick_Position.X, Wall_Distance - Wall_Close_Distance, Wall_Distance) && !bFlystick_In_Door);
+		Sign_Negative_Y->SetVisibility(FMath::IsWithin(-Flystick_Position.Y, Wall_Distance - Wall_Close_Distance, Wall_Distance) && !bFlystick_In_Door);
+		Sign_Positive_X->SetVisibility(FMath::IsWithin(+Flystick_Position.X, Wall_Distance - Wall_Close_Distance, Wall_Distance) && !bFlystick_In_Door);
+		Sign_Positive_Y->SetVisibility(FMath::IsWithin(+Flystick_Position.Y, Wall_Distance - Wall_Close_Distance, Wall_Distance) && !bFlystick_In_Door);
+
+		Sign_Material_Dynamic_->SetScalarParameterValue("SignOpacity", Sign_Opacity);
+	}
+	else
+	{
+		Sign_Negative_X->SetVisibility(false);
+		Sign_Negative_Y->SetVisibility(false);
+		Sign_Positive_X->SetVisibility(false);
+		Sign_Positive_Y->SetVisibility(false);
+	}
+}
diff --git a/Source/CAVEOverlay/Public/CAVEOverlay.h b/Source/CAVEOverlay/Public/CAVEOverlay.h
new file mode 100644
index 0000000000000000000000000000000000000000..9c40bae6cc3ece9f571c7513dbde291ec1670486
--- /dev/null
+++ b/Source/CAVEOverlay/Public/CAVEOverlay.h
@@ -0,0 +1,15 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Modules/ModuleManager.h"
+
+class FCAVEOverlayModule : public IModuleInterface
+{
+public:
+
+	/** IModuleInterface implementation */
+	virtual void StartupModule() override;
+	virtual void ShutdownModule() override;
+};
diff --git a/Source/CAVEOverlay/Public/CAVEOverlayController.h b/Source/CAVEOverlay/Public/CAVEOverlayController.h
new file mode 100644
index 0000000000000000000000000000000000000000..af238828aea072adf42eb07f02e502c6230507b1
--- /dev/null
+++ b/Source/CAVEOverlay/Public/CAVEOverlayController.h
@@ -0,0 +1,101 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "GameFramework/Actor.h"
+#include "DoorOverlayData.h"
+#include "Engine/Engine.h"
+#include <array>
+#include "Components/StaticMeshComponent.h"
+#include "Materials/Material.h"
+#include "Materials/MaterialInstanceDynamic.h"
+#include "DisplayClusterExtensions/Public/VirtualRealityPawn.h"
+#include "DisplayCluster/Public/DisplayClusterSceneComponent.h"
+#include "CAVEOverlayController.generated.h"
+
+DECLARE_LOG_CATEGORY_EXTERN(LogCAVEOverlay, Log, All);
+
+UCLASS()
+class CAVEOVERLAY_API ACAVEOverlayController : public AActor
+{
+	GENERATED_BODY()
+public:
+	ACAVEOverlayController();
+protected:
+	// Called when the game starts or when spawned
+	virtual void BeginPlay() override;
+	virtual void PostInitializeComponents() override;
+
+private:
+	//Execution Modes
+	bool bHMD_Mode = false;
+	bool bDisplay_Cluster_Mode = false;
+
+	//Screen Types
+	enum EScreen_Type { SCREEN_MASTER, SCREEN_NORMAL, SCREEN_DOOR_PARTIAL, SCREEN_DOOR };
+
+	EScreen_Type Screen_Type = SCREEN_NORMAL;
+	const std::array<FString, 4> Screens_Door = {"node_bul_left_eye", "node_bul_right_eye", "node_bll_left_eye", "node_bll_right_eye"};
+	const std::array<FString, 4> Screens_Door_Partial = {"node_bur_left_eye", "node_bur_right_eye", "node_blr_left_eye", "node_blr_right_eye"};
+	const std::array<FString, 5> Screens_FPS = {"node_rur_left_eye", "node_rur_right_eye", "node_lur_left_eye", "node_lur_right_eye", "node_main"};
+	const FString Screen_Main = "node_main";
+
+	//Door Mode
+	enum EDoor_Mode { DOOR_PARTIALLY_OPEN = 0, DOOR_OPEN = 1, DOOR_CLOSED = 2, DOOR_DEBUG = 3, DOOR_NUM_MODES = 4 };
+
+	const FString Door_Mode_Names[DOOR_NUM_MODES] = {"Partially Open", "Open", "Closed", "Debug"};
+	EDoor_Mode Door_Current_Mode = DOOR_PARTIALLY_OPEN;
+	const float Door_Opening_Width_Relative = 0.522; //%, used for the overlay width on the screen
+	const float Door_Opening_Width_Absolute = 165; //cm, used for the non tape part at the door
+	const float Wall_Distance = 262.5; //cm, distance from center to a wall, *2 = wall width
+	const float Wall_Close_Distance = 75; //cm, the distance considered to be too close to the walls
+	const float Wall_Fade_Distance = 35; //cm, the distance over which the tape is faded
+	const float Wall_Warning_Distance = 40; //cm, distance on which the tape turns red, measured from wall
+	float Door_Current_Opening_Width_Absolute = 0;
+
+	//Overlay
+	TSubclassOf<class UDoorOverlayData> Overlay_Class;
+	UDoorOverlayData* Overlay;
+
+	//Geometry and Material
+	UStaticMeshComponent* CreateMeshComponent(const FName& Name, UStaticMesh* Mesh, USceneComponent* Parent);
+	UMaterial* Tape_Material = nullptr;
+	UMaterial* Sign_Material = nullptr;
+	float CalculateOpacityFromPosition(FVector Position);
+	bool PositionInDoorOpening(FVector Position);
+
+	//Pawn Components
+	bool bAttached = false;
+	void RefreshPawnComponents();
+	AVirtualRealityPawn* Player_Pawn;
+	UDisplayClusterSceneComponent* Cave_Origin;
+	UDisplayClusterSceneComponent* Shutter_Glasses;
+
+public:
+
+	// Called every frame
+	virtual void Tick(float DeltaTime) override;
+
+	void CycleDoorType();
+	void SetDoorMode(EDoor_Mode M);
+
+	//Signs and Banners
+	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true")) USceneComponent* Root = nullptr;
+	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true")) USceneComponent* Tape_Root = nullptr;
+	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true")) USceneComponent* Sign_Root = nullptr;
+
+	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true")) UStaticMeshComponent* Tape_Negative_Y = nullptr;
+	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true")) UStaticMeshComponent* Tape_Negative_X = nullptr;
+	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true")) UStaticMeshComponent* Tape_Positive_Y = nullptr;
+	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true")) UStaticMeshComponent* Tape_Positive_X = nullptr;
+
+	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true")) UStaticMeshComponent* Sign_Negative_Y = nullptr;
+	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true")) UStaticMeshComponent* Sign_Negative_X = nullptr;
+	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true")) UStaticMeshComponent* Sign_Positive_Y = nullptr;
+	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true")) UStaticMeshComponent* Sign_Positive_X = nullptr;
+
+	UMaterialInstanceDynamic* Tape_Material_Dynamic_ = nullptr;
+	UMaterialInstanceDynamic* Sign_Material_Dynamic_ = nullptr;
+	UStaticMesh* Plane_Mesh_ = nullptr;
+};
diff --git a/Source/CAVEOverlay/Public/DoorOverlayData.h b/Source/CAVEOverlay/Public/DoorOverlayData.h
new file mode 100644
index 0000000000000000000000000000000000000000..97dbb5987b79a5467e8075b1a3a5dba4569a8f2c
--- /dev/null
+++ b/Source/CAVEOverlay/Public/DoorOverlayData.h
@@ -0,0 +1,30 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Blueprint/UserWidget.h"
+#include "TextBlock.h"
+#include "Border.h"
+#include "DoorOverlayData.generated.h"
+
+/**
+ * 
+ */
+UCLASS()
+class CAVEOVERLAY_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;
+
+	UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
+	UTextBlock* FPS;
+};