diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..56a33f06425feaaf69a41c710b30f9f990a3dbb6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,74 @@
+# 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
+
+# Built data for maps
+*_BuiltData.uasset
+
+# 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/*
\ No newline at end of file
diff --git a/Resources/Icon128.png b/Resources/Icon128.png
new file mode 100644
index 0000000000000000000000000000000000000000..77dcd239fa6f636b8032bdb878d85b8eacb8ef7f
Binary files /dev/null and b/Resources/Icon128.png differ
diff --git a/Source/nDisplayExtensions/Private/DisplayClusterGameModeCAVE.cpp b/Source/nDisplayExtensions/Private/DisplayClusterGameModeCAVE.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..90f74ef07c5398e35cccdca6eba71554a06aa4f2
--- /dev/null
+++ b/Source/nDisplayExtensions/Private/DisplayClusterGameModeCAVE.cpp
@@ -0,0 +1,8 @@
+#include "DisplayClusterGameModeCAVE.h"
+#include "DisplayClusterPawnCAVE.h"
+
+ADisplayClusterGameModeCAVE::ADisplayClusterGameModeCAVE() : Super()
+{
+	if (!bIsDisplayClusterActive) return;
+	DefaultPawnClass = ADisplayClusterPawnCAVE::StaticClass();
+}
diff --git a/Source/nDisplayExtensions/Private/DisplayClusterPawnBase.cpp b/Source/nDisplayExtensions/Private/DisplayClusterPawnBase.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..270e3f17e86eca27e4a5ce3d22b4901766d7762c
--- /dev/null
+++ b/Source/nDisplayExtensions/Private/DisplayClusterPawnBase.cpp
@@ -0,0 +1,153 @@
+#include "DisplayClusterPawnBase.h"
+
+#include "Engine/World.h"
+#include "Game/IDisplayClusterGameManager.h"
+#include "GameFramework/WorldSettings.h"
+#include "DisplayClusterSceneComponentSyncParent.h"
+#include "DisplayClusterSettings.h"
+#include "DisplayClusterGameMode.h"
+#include "IDisplayCluster.h"
+#include "Kismet/GameplayStatics.h"
+
+ADisplayClusterPawnBase::ADisplayClusterPawnBase(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
+{
+	MovementComponent                         = CreateDefaultSubobject<UFloatingPawnMovement>     (TEXT("MovementComponent0"));
+	MovementComponent->UpdatedComponent       = RootComponent;
+
+	RotatingComponent                         = CreateDefaultSubobject<URotatingMovementComponent>(TEXT("RotatingComponent0"));
+	RotatingComponent->UpdatedComponent       = RootComponent;
+	RotatingComponent->bRotationInLocalSpace  = true;
+	RotatingComponent->PivotTranslation       = FVector::ZeroVector;
+	RotatingComponent->RotationRate           = FRotator::ZeroRotator;
+
+	RotatingComponent2                        = CreateDefaultSubobject<URotatingMovementComponent>(TEXT("RotatingComponent1"));
+	RotatingComponent2->UpdatedComponent      = RootComponent;
+	RotatingComponent2->bRotationInLocalSpace = false;
+	RotatingComponent2->PivotTranslation      = FVector::ZeroVector;
+	RotatingComponent2->RotationRate          = FRotator::ZeroRotator;
+
+	BaseTurnRate   = 45.f;
+	BaseLookUpRate = 45.f;
+}
+
+void ADisplayClusterPawnBase::MoveForward (float Value)
+{
+  if (Value == 0.f) return;
+	AddMovementInput((TranslationDirection ? TranslationDirection : RootComponent)->GetForwardVector(), Value);
+}
+void ADisplayClusterPawnBase::MoveRight   (float Value)
+{
+  if (Value == 0.f) return;
+  AddMovementInput((TranslationDirection ? TranslationDirection : RootComponent)->GetRightVector  (), Value);
+}
+void ADisplayClusterPawnBase::MoveUp      (float Value)
+{
+  if (Value == 0.f) return;
+  AddMovementInput((TranslationDirection ? TranslationDirection : RootComponent)->GetUpVector     (), Value);
+}
+void ADisplayClusterPawnBase::TurnAtRate  (float Rate )
+{
+	if (IDisplayCluster::Get().GetOperationMode() == EDisplayClusterOperationMode::Cluster)
+	{
+    if (!RotatingComponent->UpdatedComponent) return;
+		auto Manager = IDisplayCluster::Get().GetGameMgr();
+    if (!Manager) return;
+    auto Camera = Manager->GetActiveCamera();
+    if (!Camera ) return;
+
+		const FTransform TransformToRotate  = RotatingComponent->UpdatedComponent->GetComponentTransform();
+		const FVector    RotateAroundPivot  = TransformToRotate.InverseTransformPositionNoScale(Camera->GetComponentLocation());
+		RotatingComponent->PivotTranslation = RotateAroundPivot;
+		RotatingComponent->RotationRate     = FRotator(RotatingComponent->RotationRate.Pitch, Rate * BaseTurnRate, 0.f);
+	}
+  else if (Rate != 0.f)
+		AddControllerYawInput(BaseTurnRate * Rate * GetWorld()->GetDeltaSeconds() * CustomTimeDilation);
+}
+void ADisplayClusterPawnBase::TurnAtRate2 (float Rate )
+{
+	if (IDisplayCluster::Get().GetOperationMode() == EDisplayClusterOperationMode::Cluster)
+	{
+    if (!RotatingComponent2->UpdatedComponent) return;
+    auto Manager = IDisplayCluster::Get().GetGameMgr();
+    if (!Manager) return;
+    auto Camera  = Manager->GetActiveCamera();
+    if (!Camera ) return;
+
+		const FTransform TransformToRotate   = RotatingComponent2->UpdatedComponent->GetComponentTransform();
+		const FVector    RotateAroundPivot   = TransformToRotate.InverseTransformPositionNoScale(Camera->GetComponentLocation());
+		RotatingComponent2->PivotTranslation = RotateAroundPivot;
+		RotatingComponent2->RotationRate     = FRotator(RotatingComponent2->RotationRate.Pitch, Rate * BaseTurnRate, 0.f);
+	}
+	else if (Rate != 0.f)
+		AddControllerYawInput(BaseTurnRate * Rate * GetWorld()->GetDeltaSeconds() * CustomTimeDilation);
+}
+void ADisplayClusterPawnBase::LookUpAtRate(float Rate )
+{
+	if (IDisplayCluster::Get().GetOperationMode() == EDisplayClusterOperationMode::Cluster)
+	{
+		//@note: usually CAVE-like systems don't use roll and pitch rotation since it can cause dizziness.
+#if 0
+		//@todo: rotate around active camera
+    auto Manager = IDisplayCluster::Get().GetGameMgr();
+    if (!Manager) return;
+    auto Camera = Manager->GetActiveCamera();
+    if (!Camera ) return;
+
+		RotatingComponent->bRotationInLocalSpace = true;
+		RotatingComponent->PivotTranslation      = FVector::ZeroVector;
+		
+#endif
+	}
+	else if (Rate != 0.f)
+	{
+		AddControllerPitchInput(BaseTurnRate * Rate * GetWorld()->GetDeltaSeconds() * CustomTimeDilation);
+	}
+}
+
+void                    ADisplayClusterPawnBase::BeginPlay           ()
+{
+	Super::BeginPlay();
+
+	if (!IDisplayCluster::Get().IsModuleInitialized() || !IDisplayCluster::Get().IsAvailable()) return;
+
+	auto IsCluster = (IDisplayCluster::Get().GetOperationMode() == EDisplayClusterOperationMode::Cluster);
+	bUseControllerRotationYaw   = !IsCluster;
+	bUseControllerRotationPitch = !IsCluster;
+	bUseControllerRotationRoll  = !IsCluster;
+
+  TArray<AActor*> SettingsActors;
+  UGameplayStatics::GetAllActorsOfClass(GetWorld(), ADisplayClusterSettings::StaticClass(), SettingsActors);
+  if (SettingsActors.Num() == 0) return;
+
+  ADisplayClusterSettings* Settings = Cast<ADisplayClusterSettings>(SettingsActors[0]);
+  MovementComponent->MaxSpeed     = Settings->MovementMaxSpeed;
+  MovementComponent->Acceleration = Settings->MovementAcceleration;
+  MovementComponent->Deceleration = Settings->MovementDeceleration;
+  MovementComponent->TurningBoost = Settings->MovementTurningBoost;
+  BaseTurnRate                    = Settings->RotationSpeed;
+  BaseLookUpRate                  = Settings->RotationSpeed;
+}
+void                    ADisplayClusterPawnBase::Tick                (float DeltaSeconds)
+{
+	Super::Tick(DeltaSeconds);
+	const float Mult = GetWorld()->GetWorldSettings()->WorldToMeters / 100.f;
+	SetActorScale3D(FVector(Mult, Mult, Mult));
+}
+UPawnMovementComponent* ADisplayClusterPawnBase::GetMovementComponent() const
+{
+  return MovementComponent;
+}
+
+void ADisplayClusterPawnBase::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
+{
+	check(PlayerInputComponent);
+	Super::SetupPlayerInputComponent(PlayerInputComponent);
+	if (PlayerInputComponent)
+	{
+		PlayerInputComponent->BindAxis("MoveForward", this, &ADisplayClusterPawnBase::MoveForward );
+		PlayerInputComponent->BindAxis("MoveRight"  , this, &ADisplayClusterPawnBase::MoveRight   );
+		PlayerInputComponent->BindAxis("MoveUp"     , this, &ADisplayClusterPawnBase::MoveUp      );
+		PlayerInputComponent->BindAxis("TurnRate"   , this, &ADisplayClusterPawnBase::TurnAtRate2 );
+		PlayerInputComponent->BindAxis("LookUpRate" , this, &ADisplayClusterPawnBase::LookUpAtRate);
+	}
+}
diff --git a/Source/nDisplayExtensions/Private/DisplayClusterPawnCAVE.cpp b/Source/nDisplayExtensions/Private/DisplayClusterPawnCAVE.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a50b178c960acc9bd21ef00ba07c70751241b02f
--- /dev/null
+++ b/Source/nDisplayExtensions/Private/DisplayClusterPawnCAVE.cpp
@@ -0,0 +1,17 @@
+#include "DisplayClusterPawnCAVE.h"
+
+#include "Game/IDisplayClusterGameManager.h"
+#include "IDisplayCluster.h"
+
+void ADisplayClusterPawnCAVE::BeginPlay()
+{
+  Flystick = IDisplayCluster::Get().GetGameMgr()->GetNodeById("flystick_tracked");
+}
+void ADisplayClusterPawnCAVE::MoveForward(float value)
+{
+  if (!Flystick)
+    Flystick = IDisplayCluster::Get().GetGameMgr()->GetNodeById("flystick_tracked");
+  if (!Flystick || value == 0.0f)
+    return;
+  AddMovementInput(Flystick->GetForwardVector(), value);
+}
diff --git a/Source/nDisplayExtensions/Private/nDisplayExtensions.cpp b/Source/nDisplayExtensions/Private/nDisplayExtensions.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8b2d5aadf5dd4a4a3f1d9405205cc4f530ac3b9c
--- /dev/null
+++ b/Source/nDisplayExtensions/Private/nDisplayExtensions.cpp
@@ -0,0 +1,16 @@
+#include "nDisplayExtensions.h"
+
+#define LOCTEXT_NAMESPACE "FnDisplayExtensionsModule"
+
+void FnDisplayExtensionsModule::StartupModule()
+{
+
+}
+void FnDisplayExtensionsModule::ShutdownModule()
+{
+
+}
+
+#undef LOCTEXT_NAMESPACE
+
+IMPLEMENT_MODULE(FnDisplayExtensionsModule, nDisplayExtensions)
\ No newline at end of file
diff --git a/Source/nDisplayExtensions/Public/DisplayClusterGameModeCAVE.h b/Source/nDisplayExtensions/Public/DisplayClusterGameModeCAVE.h
new file mode 100644
index 0000000000000000000000000000000000000000..2c68f15d371f05d18fb4f6fc97196b3e772fe0b6
--- /dev/null
+++ b/Source/nDisplayExtensions/Public/DisplayClusterGameModeCAVE.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "DisplayClusterGameMode.h"
+#include "DisplayClusterGameModeCAVE.generated.h"
+
+UCLASS()
+class NDISPLAYEXTENSIONS_API ADisplayClusterGameModeCAVE : public ADisplayClusterGameMode
+{
+	GENERATED_BODY()
+	
+public:
+	ADisplayClusterGameModeCAVE();
+};
\ No newline at end of file
diff --git a/Source/nDisplayExtensions/Public/DisplayClusterPawnBase.h b/Source/nDisplayExtensions/Public/DisplayClusterPawnBase.h
new file mode 100644
index 0000000000000000000000000000000000000000..5eb76f52ff42409c369b56f14f3ff7f3e0403217
--- /dev/null
+++ b/Source/nDisplayExtensions/Public/DisplayClusterPawnBase.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "Components/InputComponent.h"
+#include "GameFramework/FloatingPawnMovement.h"
+#include "GameFramework/PawnMovementComponent.h"
+#include "GameFramework/RotatingMovementComponent.h"
+#include "CoreMinimal.h"
+#include "DisplayClusterPawn.h"
+#include "DisplayClusterPawnBase.generated.h"
+
+UCLASS()
+class NDISPLAYEXTENSIONS_API ADisplayClusterPawnBase : public ADisplayClusterPawn
+{
+  GENERATED_UCLASS_BODY()
+
+public:
+  UFUNCTION(BlueprintCallable, Category = "Pawn") virtual void MoveForward (float Value);
+  UFUNCTION(BlueprintCallable, Category = "Pawn") virtual void MoveRight   (float Value);
+  UFUNCTION(BlueprintCallable, Category = "Pawn") virtual void MoveUp      (float Value);
+  UFUNCTION(BlueprintCallable, Category = "Pawn") virtual void TurnAtRate  (float Rate );
+  UFUNCTION(BlueprintCallable, Category = "Pawn") virtual void TurnAtRate2 (float Rate );
+  UFUNCTION(BlueprintCallable, Category = "Pawn") virtual void LookUpAtRate(float Rate );
+
+  virtual void                    BeginPlay           ()                         override;
+  virtual void                    Tick                (float DeltaSeconds)       override;
+  virtual UPawnMovementComponent* GetMovementComponent()                   const override;
+
+  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pawn") float BaseTurnRate  ;
+  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pawn") float BaseLookUpRate;
+
+protected:
+  virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
+
+  UPROPERTY(Category = Pawn, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) UFloatingPawnMovement*      MovementComponent ;
+  UPROPERTY(Category = Pawn, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) URotatingMovementComponent* RotatingComponent ;
+  UPROPERTY(Category = Pawn, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) URotatingMovementComponent* RotatingComponent2;
+};
\ No newline at end of file
diff --git a/Source/nDisplayExtensions/Public/DisplayClusterPawnCAVE.h b/Source/nDisplayExtensions/Public/DisplayClusterPawnCAVE.h
new file mode 100644
index 0000000000000000000000000000000000000000..48c64c90681f0aadf6b293247bfc3a9df5eb5b0b
--- /dev/null
+++ b/Source/nDisplayExtensions/Public/DisplayClusterPawnCAVE.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "DisplayClusterPawnBase.h"
+#include "DisplayClusterSceneComponent.h"
+#include "DisplayClusterPawnCAVE.generated.h"
+
+UCLASS()
+class NDISPLAYEXTENSIONS_API ADisplayClusterPawnCAVE : public ADisplayClusterPawnBase
+{
+  GENERATED_BODY()
+
+public:
+  virtual void BeginPlay() override;
+  virtual void MoveForward(float value) override;
+
+  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Pawn")
+  UDisplayClusterSceneComponent* Flystick = nullptr;
+};
diff --git a/Source/nDisplayExtensions/Public/nDisplayExtensions.h b/Source/nDisplayExtensions/Public/nDisplayExtensions.h
new file mode 100644
index 0000000000000000000000000000000000000000..d5e9096991ad70694240b1ce329f2222ba2456cb
--- /dev/null
+++ b/Source/nDisplayExtensions/Public/nDisplayExtensions.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Modules/ModuleManager.h"
+
+class NDISPLAYEXTENSIONS_API FnDisplayExtensionsModule : public IModuleInterface
+{
+public:
+  virtual void StartupModule() override;
+  virtual void ShutdownModule() override;
+};
diff --git a/Source/nDisplayExtensions/nDisplayExtensions.Build.cs b/Source/nDisplayExtensions/nDisplayExtensions.Build.cs
new file mode 100644
index 0000000000000000000000000000000000000000..22f9d9dcf3e85da6b4565f32f5543364fc735762
--- /dev/null
+++ b/Source/nDisplayExtensions/nDisplayExtensions.Build.cs
@@ -0,0 +1,52 @@
+using UnrealBuildTool;
+
+public class nDisplayExtensions : ModuleRules
+{
+  public nDisplayExtensions(ReadOnlyTargetRules Target) : base(Target)
+  {
+    PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
+
+    PublicIncludePaths.AddRange(
+      new string[]
+      {
+
+      }
+      );
+
+
+    PrivateIncludePaths.AddRange(
+      new string[]
+      {
+
+      }
+      );
+
+
+    PublicDependencyModuleNames.AddRange(
+      new string[]
+      {
+        "Core",
+        "CoreUObject",
+        "DisplayCluster",
+        "Engine",
+        "InputCore"
+      }
+      );
+
+
+    PrivateDependencyModuleNames.AddRange(
+      new string[]
+      {
+
+      }
+      );
+
+
+    DynamicallyLoadedModuleNames.AddRange(
+      new string[]
+      {
+
+      }
+      );
+  }
+}
diff --git a/nDisplayExtensions.uplugin b/nDisplayExtensions.uplugin
new file mode 100644
index 0000000000000000000000000000000000000000..31dbd3836665bce4091212b10f9e0d711e258bc8
--- /dev/null
+++ b/nDisplayExtensions.uplugin
@@ -0,0 +1,29 @@
+{
+  "FileVersion": 3,
+  "Version": 1,
+  "VersionName": "1.0",
+  "FriendlyName": "nDisplayExtensions",
+  "Description": "",
+  "Category": "Other",
+  "CreatedBy": "",
+  "CreatedByURL": "",
+  "DocsURL": "",
+  "MarketplaceURL": "",
+  "SupportURL": "",
+  "CanContainContent": true,
+  "IsBetaVersion": false,
+  "Installed": false,
+  "Modules": [
+    {
+      "Name": "nDisplayExtensions",
+      "Type": "Developer",
+      "LoadingPhase": "Default"
+    }
+  ],
+  "Plugins": [
+    {
+      "Name": "nDisplay",
+      "Enabled": true
+    }
+  ]
+}
\ No newline at end of file