diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..c0b420ee6433d9a9aca1618cc324b8250d7dc242 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,93 @@ +#------------------------------------------------------------------------------- +# Copyright (c) 2020 RWTH Aachen University, Germany, +# Virtual Reality & Immersive Visualisation Group. +#------------------------------------------------------------------------------- + +# The include file can be change to either be removed or reference a specific commit. + +include: + - project: 'vr-group/unreal-development/unreal-ci' + ref: master + file: '/shared_scripts.yml' + +# In this file you are able to configure your plugins pipeline. +# If you want to customize something, either overwrite things that are defined in the shared_scripts repository, +# or remove the "extends" and write your own scripts +# +# If you want your pipeline to run on every commit, just remove the "only" blocks. Keep in mind, that a build +# can take some time. +# +# If you want to alter the unreal-building process two variables are defined for either changing the CLIENT_CONFIG or +# for adding EXTRA_ARGS to the building process +# +# For the generate stage, you can specify needed dependencies in GEN_DEPENDENCIES with [Branch@PluginFolder] as key +# Example: +# +# Generate_Project: +# only: ['web', 'schedules'] +# extends: .Generate_Project_ +# variables: +# GEN_TEMPLATE_REPO: "https://devhub.vr.rwth-aachen.de/VR-Group/unreal-development/unrealprojecttemplate.git" +# GEN_TEMPLATE_BRANCH: "4.26" +# GEN_DEPENDENCIES: "( +# [4.26@RWTHVRToolkit]='https://devhub.vr.rwth-aachen.de/VR-Group/unreal-development/Plugins/rwth-vr-toolkit.git' +# [4.26@UniversalLogging]='https://devhub.vr.rwth-aachen.de/VR-Group/unreal-development/Plugins/universallogging.git' +# )" +# +# You can uncomment the deploy lines to deploy your project to the CAVE/VRDev. This only makes sense, if your plugin works +# with a generated project. + +stages: + - generate + - build + - deploy + +Generate_Project: + only: ['web', 'schedules'] + extends: .Generate_Project_ + variables: + RUN_SETUP: "false" + +Build_Windows: + only: ['web', 'schedules'] + extends: .Build_Windows_ + tags: + - windows + - unreal-4.26 + variables: + GIT_STRATEGY: none + GIT_CHECKOUT: "false" + # CLIENT_CONFIG: "Shipping" + CLIENT_CONFIG: "DebugGame" + needs: + - job: "Generate_Project" + artifacts: true + +Build_CentOS: + only: ['web', 'schedules'] + extends: .Build_CentOS_ + tags: + - centos + - unreal-4.26 + variables: + GIT_STRATEGY: none + GIT_CHECKOUT: "false" + # CLIENT_CONFIG: "Shipping" + CLIENT_CONFIG: "DebugGame" + needs: + - job: "Generate_Project" + artifacts: true + +Deploy_CAVE: + only: ['web', 'schedules'] + extends: .Deploy_CAVE_ + needs: + - job: "Build_CentOS" + artifacts: true + +Deploy_Windows: + only: ['web', 'schedules'] + extends: .Deploy_VRDev_ + needs: + - job: "Build_Windows" + artifacts: true diff --git a/README.md b/README.md index 29d51505f11dd28f818c9cb5e806213343c253c0..362b5f152a49cc8916748d09d2c9f7701cae9314 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ The HMD Simulator uses the keyboard and mouse to grab the input that is needed t Do a [VR Preview launch](Resources/VRLaunch.png) and you will see something similar to this:  Currently, the HMD Simulator emulated the two controllers and the position of the HMD. +If SteamVR is running and a real HMD is detected it is used instead. If you want to explicitly use the HMDSimulator, switch off your HMD or unplug it and close SteamVR. Then restart Unreal und do a [VR Preview launch](Resources/VRLaunch.png). + ### Camera Movement You can move around with the Mouse + WASD @@ -33,3 +35,6 @@ You can select a controller to control with either LeftShift (Left Controller) o The emulated controllers also offer 3 virtual buttons at the moment. The Trigger, Grab and Menu Button. These can be controlled with T, G or M respectively. If you select a controller (or more) and press one of these keys the controller sends a respective button press to Unreal, that you can use in your Blueprints (ideally via ActionMappings, see Configuration). If setup correctly you can e.g. Grab objects in the world and move them:  + +### Packaging +The HMD Simulator can be packed together with your game and only turns on, if you launch your game in VR mode (commandline parameter `-vr`) and additionally provide the `-hmd-simulator` parameter. diff --git a/Resources/Icon128.png b/Resources/Icon128.png index bdc1619ab30afbf733e4a9799c1c5fbbb330ab0d..bf1e63433580d9fa017392a3a6c06c2deb291021 100644 Binary files a/Resources/Icon128.png and b/Resources/Icon128.png differ diff --git a/Source/HMDSimulator/Private/HMDSimulationManager.cpp b/Source/HMDSimulator/Private/HMDSimulationManager.cpp index 08dbde2be74a9c39cda09d252473717557be702b..e552a8737be0cb427d5a67dd18a9341d9e21e91b 100644 --- a/Source/HMDSimulator/Private/HMDSimulationManager.cpp +++ b/Source/HMDSimulator/Private/HMDSimulationManager.cpp @@ -9,33 +9,30 @@ UHMDSimulationManager::UHMDSimulationManager() MoveKeyPressed.Add(EKeys::A, false); MoveKeyPressed.Add(EKeys::S, false); MoveKeyPressed.Add(EKeys::D, false); +} - WorldInitializationHandle = FWorldDelegates::OnPostWorldInitialization.AddLambda([this](UWorld* World, const UWorld::InitializationValues Values) - { - CurrentWorld = World; - WorldBeginPlayHandle = World->OnWorldBeginPlay.AddLambda([this]() - { - BindInputEvents(); - HMD.Reset(); - LeftHand.Reset(HMD.AccumulatedRotation.GetForwardVector()*50.0f + HMD.AccumulatedRotation.GetRightVector()*-25.0f); - RightHand.Reset(HMD.AccumulatedRotation.GetForwardVector()*50.0f + HMD.AccumulatedRotation.GetRightVector()*25.0f); - }); - }); - - WorldDeinitializationHandle = FWorldDelegates::OnPreWorldFinishDestroy.AddLambda([this](UWorld* World) +void UHMDSimulationManager::OnBeginPlay(FWorldContext& InWorldContext, bool EnableInThisPlay) +{ + CurrentlyEnabled = EnableInThisPlay; + CurrentWorld = InWorldContext.World(); + + HMD.Reset(); + LeftHand.Reset(HMD.AccumulatedRotation.GetForwardVector()*50.0f + HMD.AccumulatedRotation.GetRightVector()*-25.0f); + RightHand.Reset(HMD.AccumulatedRotation.GetForwardVector()*50.0f + HMD.AccumulatedRotation.GetRightVector()*25.0f); + + if(EnableInThisPlay) { - UnbindInputEvents(); - World->OnWorldBeginPlay.Remove(WorldBeginPlayHandle); /* Unregister BeginPlay */ - }); + BindInputEvents(); + } } -UHMDSimulationManager::~UHMDSimulationManager() +void UHMDSimulationManager::OnEndPlay(FWorldContext& InWorldContext) { - /* Unregister handles to get BeginPlay Event */ - FWorldDelegates::OnPostWorldInitialization.Remove(WorldInitializationHandle); - FWorldDelegates::OnPreWorldFinishDestroy.Remove(WorldDeinitializationHandle); + UnbindInputEvents(); //Do anyway } +UHMDSimulationManager::~UHMDSimulationManager(){} + void UHMDSimulationManager::GetHeadPose(FQuat& CurrentOrientation, FVector& CurrentPosition) const { CurrentOrientation = HMD.AccumulatedRotation; @@ -351,6 +348,7 @@ bool UHMDSimulationManager::HandleMouseWheelOrGestureEvent(FSlateApplication& Sl return false; } +// The keys in this function actually are remapped on top. The enum is only used to transfer the intention to this function. void UHMDSimulationManager::Tick(const float DeltaTime, FSlateApplication& SlateApp, TSharedRef<ICursor> Cursor) { const float Speed = GetDefault<UHMDSimulatorInputSettings>()->MovementSpeed; diff --git a/Source/HMDSimulator/Public/HMDSimulationManager.h b/Source/HMDSimulator/Public/HMDSimulationManager.h index 28c8aee667a9b917a3c923b6d4b33d6fa7f8583b..3442d0cf842f30e727982e971196efd31a559049 100644 --- a/Source/HMDSimulator/Public/HMDSimulationManager.h +++ b/Source/HMDSimulator/Public/HMDSimulationManager.h @@ -44,17 +44,18 @@ public: void GetHeadPose(FQuat& CurrentOrientation, FVector& CurrentPosition) const; void GetLeftHandPose(FRotator& CurrentOrientation, FVector& CurrentPosition) const; void GetRightHandPose(FRotator& CurrentOrientation, FVector& CurrentPosition) const; - -private: - /* Delegates */ - FDelegateHandle WorldBeginPlayHandle; - FDelegateHandle WorldEndPlayHandle; - FDelegateHandle WorldDeinitializationHandle; - FDelegateHandle WorldInitializationHandle; + virtual void OnBeginPlay(FWorldContext& InWorldContext, bool EnableInThisPlay); //Called by simulated HMD + virtual void OnEndPlay(FWorldContext& InWorldContext); //Called by simulated HMD + + bool IsCurrentlyEnabled(){return CurrentlyEnabled;} + +private: void BindInputEvents(); void UnbindInputEvents(); UWorld* CurrentWorld = nullptr; + bool CurrentlyEnabled = false; + public: FControllerData HMD; FControllerData LeftHand; /* Relative to HMD */ diff --git a/Source/HMDSimulatorHMD/HMDSimulatorHMD.Build.cs b/Source/HMDSimulatorHMD/HMDSimulatorHMD.Build.cs index a44a6326d9c42fe5968427d747e45055a3416b89..332e1e3c832d40a2a25d1c490cdc19cda2952700 100644 --- a/Source/HMDSimulatorHMD/HMDSimulatorHMD.Build.cs +++ b/Source/HMDSimulatorHMD/HMDSimulatorHMD.Build.cs @@ -43,10 +43,16 @@ public class HMDSimulatorHMD : ModuleRules "RenderCore", "InputCore", "InputDevice", - "HMDSimulator" + "HMDSimulator", + "EngineSettings" // ... add private dependencies that you statically link with here ... } ); + + if (Target.bBuildEditor) + { + PrivateDependencyModuleNames.Add("UnrealEd"); + } DynamicallyLoadedModuleNames.AddRange( diff --git a/Source/HMDSimulatorHMD/Private/HMDSimulatorHMDModule.cpp b/Source/HMDSimulatorHMD/Private/HMDSimulatorHMDModule.cpp index 0225cccea8cd8f2d88c164e42c7450c5dfc4c2d4..b1f004845483cb4fb753c3fdaf2a8f8e6edad6a8 100644 --- a/Source/HMDSimulatorHMD/Private/HMDSimulatorHMDModule.cpp +++ b/Source/HMDSimulatorHMD/Private/HMDSimulatorHMDModule.cpp @@ -22,4 +22,4 @@ TSharedPtr<IXRTrackingSystem, ESPMode::ThreadSafe> FHMDSimulatorHMDModule::Creat return HMD; } -IMPLEMENT_MODULE(FHMDSimulatorHMDModule, VRSimulatorHMD) \ No newline at end of file +IMPLEMENT_MODULE(FHMDSimulatorHMDModule, HMDSimulatorHMD) \ No newline at end of file diff --git a/Source/HMDSimulatorHMD/Private/SimulatedHMD.cpp b/Source/HMDSimulatorHMD/Private/SimulatedHMD.cpp index 3fde08c865abd045ecbd97d1201ec8062eeab703..be34eb1694c9a5dca96666e4aa3321a985352982 100644 --- a/Source/HMDSimulatorHMD/Private/SimulatedHMD.cpp +++ b/Source/HMDSimulatorHMD/Private/SimulatedHMD.cpp @@ -14,6 +14,11 @@ #include "Kismet/GameplayStatics.h" #include "Misc/App.h" #include "Modules/ModuleManager.h" +#include "GeneralProjectSettings.h" + +#if WITH_EDITOR +#include "Settings/LevelEditorPlaySettings.h" +#endif DEFINE_LOG_CATEGORY_STATIC(LogHMDSimulatiorHMD, Log, All); @@ -39,12 +44,12 @@ bool FSimulatedHMD::IsHMDConnected() /* Always connected */ return true; } -bool FSimulatedHMD::IsHMDEnabled() const /* Always enabled */ +bool FSimulatedHMD::IsHMDEnabled() const { - return true; + return HMDSimulationManager && HMDSimulationManager->IsCurrentlyEnabled(); } -void FSimulatedHMD::EnableHMD(bool){} /* Always enabled */ +void FSimulatedHMD::EnableHMD(bool){} /* Enabled/Disabled automatically (see CheckActivation) */ bool FSimulatedHMD::GetHMDMonitorInfo(MonitorInfo& MonitorDesc) { @@ -60,9 +65,47 @@ void FSimulatedHMD::GetFieldOfView(float& OutHFOVInDegrees, float& OutVFOVInDegr OutVFOVInDegrees = 0.0f; } +bool FSimulatedHMD::CheckActivation() +{ +#if WITH_EDITOR + if (GIsEditor) + { + // Play in VR, but no other HMD available + if(GetDefault<ULevelEditorPlaySettings>()->LastExecutedPlayModeType == PlayMode_InVR + && GEngine->XRSystem->CountTrackedDevices(EXRTrackedDeviceType::HeadMountedDisplay) <= 1) + { + return true; + } + } +#endif + + // Launch via commandline and give `-vr -hmd-simulator` + if(FParse::Param(FCommandLine::Get(), TEXT("hmd-simulator")) + && (FParse::Param(FCommandLine::Get(), TEXT("vr")) || GetDefault<UGeneralProjectSettings>()->bStartInVR)) + { + return true; + } + + return false; +} + +void FSimulatedHMD::OnBeginPlay(FWorldContext& InWorldContext) +{ + const bool Activation = CheckActivation(); + + UE_LOG(LogHMDSimulatiorHMD, Log, TEXT("HMDSimulator Activated: %d"), Activation); + HMDSimulationManager->OnBeginPlay(InWorldContext, Activation); +} + +void FSimulatedHMD::OnEndPlay(FWorldContext& InWorldContext) +{ + HMDSimulationManager->OnEndPlay(InWorldContext); +} + bool FSimulatedHMD::EnumerateTrackedDevices(TArray<int32>& OutDevices, EXRTrackedDeviceType Type) { - if (Type == EXRTrackedDeviceType::Any || Type == EXRTrackedDeviceType::HeadMountedDisplay) + if (HMDSimulationManager && HMDSimulationManager->IsCurrentlyEnabled() && + (Type == EXRTrackedDeviceType::Any || Type == EXRTrackedDeviceType::HeadMountedDisplay)) { OutDevices.Add(HMDDeviceId); return true; @@ -80,6 +123,8 @@ float FSimulatedHMD::GetInterpupillaryDistance() const bool FSimulatedHMD::GetCurrentPose(int32 DeviceId, FQuat& CurrentOrientation, FVector& CurrentPosition) { + if(!HMDSimulationManager || !HMDSimulationManager->IsCurrentlyEnabled()) return false; + if(DeviceId == HMDDeviceId) { HMDSimulationManager->GetHeadPose(CurrentOrientation, CurrentPosition); @@ -368,7 +413,7 @@ void FSimulatedHMD::PreRenderViewFamily_RenderThread(FRHICommandListImmediate&, bool FSimulatedHMD::IsActiveThisFrame(class FViewport* InViewport) const { - return GEngine && GEngine->IsStereoscopic3D(InViewport); + return GEngine && GEngine->IsStereoscopic3D(InViewport) && HMDSimulationManager && HMDSimulationManager->IsCurrentlyEnabled(); } FSimulatedHMD::FSimulatedHMD(const FAutoRegister& AutoRegister) diff --git a/Source/HMDSimulatorHMD/Public/SimulatedHMD.h b/Source/HMDSimulatorHMD/Public/SimulatedHMD.h index f99cc02337b7f0157b93872b9ad5e6967647585b..971d24cbcf2fbef07083c06c01c841ceba94dee6 100644 --- a/Source/HMDSimulatorHMD/Public/SimulatedHMD.h +++ b/Source/HMDSimulatorHMD/Public/SimulatedHMD.h @@ -29,7 +29,10 @@ public: const static FName SystemName; /** IXRTrackingSystem interface */ - virtual FName GetSystemName() const override { return SystemName; } + virtual void OnBeginPlay(FWorldContext& InWorldContext) override; + virtual void OnEndPlay(FWorldContext& InWorldContext) override; + + virtual FName GetSystemName() const override { return SystemName; } virtual int32 GetXRSystemFlags() const override { return EXRSystemFlags::IsHeadMounted; } @@ -76,7 +79,8 @@ public: virtual void EnableHMD(bool allow = true) override; virtual bool GetHMDMonitorInfo(MonitorInfo&) override; virtual void GetFieldOfView(float& OutHFOVInDegrees, float& OutVFOVInDegrees) const override; - virtual bool IsChromaAbCorrectionEnabled() const override; + bool CheckActivation(); + virtual bool IsChromaAbCorrectionEnabled() const override; virtual FIntPoint GetIdealRenderTargetSize() const override; virtual bool OnStartGameFrame(FWorldContext& WorldContext) override; diff --git a/Source/HMDSimulatorInput/Private/HMDSimulatorInput.cpp b/Source/HMDSimulatorInput/Private/HMDSimulatorInput.cpp index 128cef0a6e42bbc96feb9493ccc0b7de9676fe37..91280b10e6a6eb473f13caa8cc632cefb1efd41d 100644 --- a/Source/HMDSimulatorInput/Private/HMDSimulatorInput.cpp +++ b/Source/HMDSimulatorInput/Private/HMDSimulatorInput.cpp @@ -3,6 +3,7 @@ #include "HMDSimulatorInput.h" #include "IXRSystemAssets.h" +#include "IXRTrackingSystem.h" #include "SimulatedMotionController.h" #include "HMDSimulator.h" @@ -27,10 +28,14 @@ void FHMDSimulatorInputModule::ShutdownModule() TSharedPtr<IInputDevice> FHMDSimulatorInputModule::CreateInputDevice(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler) { - SimulatedMotionController = MakeShared<FSimulatedMotionController>(InMessageHandler); - SimulatedMotionController->SetHMDSimulationController(FHMDSimulatorModule::Get().GetManager()); - SimulatedMotionController->InitKeys(); - return SimulatedMotionController; + if (GEngine && GEngine->XRSystem.IsValid() && (GEngine->XRSystem->GetSystemName() == TEXT("HMDSimulator"))) + { + SimulatedMotionController = MakeShared<FSimulatedMotionController>(InMessageHandler); + SimulatedMotionController->SetHMDSimulationController(FHMDSimulatorModule::Get().GetManager()); + SimulatedMotionController->InitKeys(); + return SimulatedMotionController; + } + return nullptr; } #undef LOCTEXT_NAMESPACE diff --git a/Source/HMDSimulatorInput/Private/MotionControllerAssetManager.cpp b/Source/HMDSimulatorInput/Private/MotionControllerAssetManager.cpp index 99808a95cbd9941c119dbcf99fdc55a8eb99e65e..9506475a94c759f9db99af5bef06111d34aa34ed 100644 --- a/Source/HMDSimulatorInput/Private/MotionControllerAssetManager.cpp +++ b/Source/HMDSimulatorInput/Private/MotionControllerAssetManager.cpp @@ -9,7 +9,7 @@ FMotionControllerAssetManager::~FMotionControllerAssetManager() FName FMotionControllerAssetManager::GetSystemName() const { - return "HMDSimulatorControllerAssetManager"; + return SystemName; } bool FMotionControllerAssetManager::EnumerateRenderableDevices(TArray<int32>& DeviceListOut) diff --git a/Source/HMDSimulatorInput/Private/SimulatedMotionController.cpp b/Source/HMDSimulatorInput/Private/SimulatedMotionController.cpp index 12f94a367db3b52a64bcb241192a202d5a45867f..d11236c23737fa4e0bcf6b6849d0302632062e92 100644 --- a/Source/HMDSimulatorInput/Private/SimulatedMotionController.cpp +++ b/Source/HMDSimulatorInput/Private/SimulatedMotionController.cpp @@ -57,6 +57,8 @@ void FSimulatedMotionController::SetChannelValues(int32, const FForceFeedbackVal void FSimulatedMotionController::EnumerateSources(TArray<FMotionControllerSource>& SourcesOut) const { + if(!HMDSimulationManager || !HMDSimulationManager->IsCurrentlyEnabled()) return; + SourcesOut.Add(FMotionControllerSource(LeftHandSourceId)); SourcesOut.Add(FMotionControllerSource(RightHandSourceId)); } @@ -69,6 +71,8 @@ FName FSimulatedMotionController::GetMotionControllerDeviceTypeName() const bool FSimulatedMotionController::GetControllerOrientationAndPosition(const int32, const EControllerHand DeviceHand, FRotator& OutOrientation, FVector& OutPosition, float) const { + if(!HMDSimulationManager || !HMDSimulationManager->IsCurrentlyEnabled()) return false; + switch(DeviceHand) { case EControllerHand::Left: @@ -83,6 +87,7 @@ bool FSimulatedMotionController::GetControllerOrientationAndPosition(const int32 ETrackingStatus FSimulatedMotionController::GetControllerTrackingStatus(const int32, const EControllerHand) const { + if(!HMDSimulationManager || !HMDSimulationManager->IsCurrentlyEnabled()) return ETrackingStatus::NotTracked; return ETrackingStatus::Tracked; } diff --git a/Source/HMDSimulatorInput/Public/HMDSimulatorControllerModels.h b/Source/HMDSimulatorInput/Public/HMDSimulatorControllerModels.h new file mode 100644 index 0000000000000000000000000000000000000000..fc41470bbe7fc9c7384cf58dd3e511f9adaa9492 --- /dev/null +++ b/Source/HMDSimulatorInput/Public/HMDSimulatorControllerModels.h @@ -0,0 +1,30 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "HMDSimulatorInputSettings.h" +#include "HMDSimulatorControllerModels.generated.h" + +/** + * This class only holds references to the models that are used by the MotionControllerAssetLibrary. By referencing them here, the models are cooked and packaged correctly. + */ +UCLASS() +class HMDSIMULATORINPUT_API UHMDSimulatorControllerModels : public UObject +{ + GENERATED_BODY() + + UHMDSimulatorControllerModels() { + const ConstructorHelpers::FObjectFinder<UStaticMesh> ViveFinder = ConstructorHelpers::FObjectFinder<UStaticMesh>(TEXT("StaticMesh'/Engine/VREditor/Devices/Vive/VivePreControllerMesh.VivePreControllerMesh'")); + if (ViveFinder.Succeeded()) ViveModel = ViveFinder.Object; + + const ConstructorHelpers::FObjectFinder<UStaticMesh> OculusFinder = ConstructorHelpers::FObjectFinder<UStaticMesh>(TEXT("StaticMesh'/Engine/VREditor/Devices/Oculus/OculusControllerMesh.OculusControllerMesh'")); + if (OculusFinder.Succeeded()) OculusModel = OculusFinder.Object; + + CustomModel = GetDefault<UHMDSimulatorInputSettings>()->CustomControllerMesh; + } + + UPROPERTY() UStaticMesh* ViveModel; + UPROPERTY() UStaticMesh* OculusModel; + UPROPERTY() UStaticMesh* CustomModel; +}; diff --git a/Source/HMDSimulatorInput/Public/MotionControllerAssetManager.h b/Source/HMDSimulatorInput/Public/MotionControllerAssetManager.h index b682e102842a647f86ce5be2307fb8da790b6fda..93ba0f60f8c2f7c083046a86dc0ec9634528e357 100644 --- a/Source/HMDSimulatorInput/Public/MotionControllerAssetManager.h +++ b/Source/HMDSimulatorInput/Public/MotionControllerAssetManager.h @@ -5,6 +5,7 @@ class HMDSIMULATORINPUT_API FMotionControllerAssetManager : public IXRSystemAssets { public: + const FName SystemName = TEXT("HMDSimulator"); /** Clean everything up */ virtual ~FMotionControllerAssetManager();