diff --git a/Content/Calibratio/CalibratioHud.uasset b/Content/Calibratio/CalibratioHud.uasset new file mode 100644 index 0000000000000000000000000000000000000000..f72b8fb6c7505876dc1fcc035d1ed07048865527 Binary files /dev/null and b/Content/Calibratio/CalibratioHud.uasset differ diff --git a/Content/Calibratio/CalibratioMaterial.uasset b/Content/Calibratio/CalibratioMaterial.uasset new file mode 100644 index 0000000000000000000000000000000000000000..c1ff5f0bd9e92786b74edd76e5ca51ee81db1413 Binary files /dev/null and b/Content/Calibratio/CalibratioMaterial.uasset differ diff --git a/Content/Calibratio/Cylinder.uasset b/Content/Calibratio/Cylinder.uasset new file mode 100644 index 0000000000000000000000000000000000000000..23a1be42460bdae72d9bd601de338681336e6e4f Binary files /dev/null and b/Content/Calibratio/Cylinder.uasset differ diff --git a/RWTHVRToolkit.uplugin b/RWTHVRToolkit.uplugin index a0891804774aa9a6dc48313dedd275af7cd477fa..690ed43563cb6a18ecc61882b4f323688117263a 100644 --- a/RWTHVRToolkit.uplugin +++ b/RWTHVRToolkit.uplugin @@ -15,6 +15,11 @@ "Installed": false, "EnabledByDefault": true, "Modules": [ + { + "Name": "Calibratio", + "Type": "Runtime", + "LoadingPhase": "Default" + }, { "Name": "RWTHVRToolkit", "Type": "Runtime", diff --git a/Source/Calibratio/Calibratio.Build.cs b/Source/Calibratio/Calibratio.Build.cs new file mode 100644 index 0000000000000000000000000000000000000000..ed66e1ac1b86072a467f014f24569661d284f5c5 --- /dev/null +++ b/Source/Calibratio/Calibratio.Build.cs @@ -0,0 +1,32 @@ +using UnrealBuildTool; + +public class Calibratio : ModuleRules +{ + public Calibratio(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[]{} + ); + + PrivateIncludePaths.AddRange( + new string[]{} + ); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", "CoreUObject", "Engine", "UMG", "SlateCore", "DisplayCluster", "RWTHVRToolkit" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[]{} + ); + + DynamicallyLoadedModuleNames.AddRange( + new string[]{} + ); + } +} diff --git a/Source/Calibratio/Private/Calibratio.cpp b/Source/Calibratio/Private/Calibratio.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a3a085b211c93c002c619d4e1264a8945deea291 --- /dev/null +++ b/Source/Calibratio/Private/Calibratio.cpp @@ -0,0 +1,55 @@ +#include "Calibratio.h" +#include "Kismet/GameplayStatics.h" +#include "IDisplayCluster.h" +#include "Cluster/DisplayClusterClusterEvent.h" +#include "Utility/VirtualRealityUtilities.h" + +#define LOCTEXT_NAMESPACE "FCalibratioModule" + +void FCalibratioModule::StartupModule () +{ + /* Registering console command */ + CalibratioConsoleCommand = IConsoleManager::Get().RegisterConsoleCommand(TEXT("Calibratio"), TEXT("Spawn an instance of calibratio"), + FConsoleCommandWithArgsDelegate::CreateLambda([](const TArray< FString >&) + { + if(UVirtualRealityUtilities::IsRoomMountedMode()){ + /* Emitting cluster event to spawn on all nodes in sync*/ + FDisplayClusterClusterEventJson ClusterEvent; + ClusterEvent.Name = "CalibratioSpawn"; + ClusterEvent.Category = "CalibratioSpawner"; + IDisplayCluster::Get().GetClusterMgr()->EmitClusterEventJson(ClusterEvent, false); + } else { + SpawnCalibratio(); + } + })); + + + /* Register cluster event listening */ + IDisplayClusterClusterManager* ClusterManager = IDisplayCluster::Get().GetClusterMgr(); + if (ClusterManager && !ClusterEventListenerDelegate.IsBound()) + { + ClusterEventListenerDelegate = FOnClusterEventJsonListener::CreateLambda([](const FDisplayClusterClusterEventJson& Event) + { + if (Event.Category.Equals("CalibratioSpawner") && Event.Name.Equals("CalibratioSpawn")) + { + SpawnCalibratio(); + } + }); + ClusterManager->AddClusterEventJsonListener(ClusterEventListenerDelegate); + } +} +void FCalibratioModule::ShutdownModule() +{ + IConsoleManager::Get().UnregisterConsoleObject(CalibratioConsoleCommand); + IDisplayCluster::Get().GetClusterMgr()->RemoveClusterEventJsonListener(ClusterEventListenerDelegate); +} + +void FCalibratioModule::SpawnCalibratio() +{ + if (UGameplayStatics::GetActorOfClass(GEngine->GetCurrentPlayWorld(), ACalibratioActor::StaticClass()) != nullptr) return; + GEngine->GetCurrentPlayWorld()->SpawnActor<ACalibratioActor>(); +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FCalibratioModule, Calibratio) \ No newline at end of file diff --git a/Source/Calibratio/Private/CalibratioActor.cpp b/Source/Calibratio/Private/CalibratioActor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ebf58ec63d04156f5ce3b6d031285c58fd054899 --- /dev/null +++ b/Source/Calibratio/Private/CalibratioActor.cpp @@ -0,0 +1,323 @@ +#include "CalibratioActor.h" + +#include "Components/StaticMeshComponent.h" +#include "Cluster/DisplayClusterClusterEvent.h" +#include "IDisplayCluster.h" +#include "TimerManager.h" +#include "CalibratioOverlay.h" +#include "GameFramework/InputSettings.h" +#include "Utility/VirtualRealityUtilities.h" + +ACalibratioActor::ACalibratioActor() +{ + PrimaryActorTick.bCanEverTick = true; + AutoReceiveInput = EAutoReceiveInput::Player0; + + /* Loads needed Assets */ + UStaticMesh* CylinderMesh = nullptr; + UVirtualRealityUtilities::LoadClass("WidgetBlueprint'/RWTHVRToolkit/Calibratio/CalibratioHud'", Overlay_Class); + UVirtualRealityUtilities::LoadAsset("StaticMesh'/RWTHVRToolkit/Calibratio/Cylinder'", CylinderMesh); + UVirtualRealityUtilities::LoadAsset("Material'/RWTHVRToolkit/Calibratio/CalibratioMaterial'", BaseMaterial); + + /* Create Mesh component and initialize */ + Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh")); + Mesh->SetupAttachment(RootComponent); + Mesh->SetStaticMesh(CylinderMesh); + Mesh->SetRelativeScale3D(FVector(1,1,0.1f)); //Make it a Disk + RootComponent = Mesh; +} + +// Called when the game starts or when spawned +void ACalibratioActor::BeginPlay() +{ + Super::BeginPlay(); + + /* Register cluster event listener */ + IDisplayClusterClusterManager* ClusterManager = IDisplayCluster::Get().GetClusterMgr(); + if (ClusterManager && !ClusterEventListenerDelegate.IsBound()) + { + ClusterEventListenerDelegate = FOnClusterEventJsonListener::CreateUObject(this, &ACalibratioActor::HandleClusterEvent); + ClusterManager->AddClusterEventJsonListener(ClusterEventListenerDelegate); + } + + /* Create Overlay */ + Overlay = CreateWidget<UCalibratioOverlay>(GetWorld()->GetFirstPlayerController(), Overlay_Class); + Overlay->AddToViewport(0); + Overlay->SetThresholds(MinRotation, MaxRotation, Threshold); + Overlay->SetOwner(this); + + /* Bind Buttons */ + Overlay->ResettingButton->OnClicked.AddDynamic(this, &ACalibratioActor::ClusterReset); + Overlay->IncreaseThresholdButton->OnClicked.AddDynamic(this, &ACalibratioActor::ClusterIncreaseThreshold); + Overlay->DecreaseThresholdButton->OnClicked.AddDynamic(this, &ACalibratioActor::ClusterDecreaseThreshold); + + /* Hide this overlay on all clients */ + if (UVirtualRealityUtilities::IsSlave()) + { + Overlay->SetVisibility(ESlateVisibility::Hidden); + } +} + +void ACalibratioActor::PostInitializeComponents() +{ + Super::PostInitializeComponents(); + + /* Create dynamic materials at runtime (Not constructor) */ + DynamicMaterial = UMaterialInstanceDynamic::Create(BaseMaterial, RootComponent); + DynamicMaterial->SetVectorParameterValue("Color", FColor::Red); + Mesh->SetMaterial(0, DynamicMaterial); +} + +void ACalibratioActor::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + /* Remove from Cluster events */ + IDisplayClusterClusterManager* ClusterManager = IDisplayCluster::Get().GetClusterMgr(); + if (ClusterManager && ClusterEventListenerDelegate.IsBound()) + { + ClusterManager->RemoveClusterEventJsonListener(ClusterEventListenerDelegate); + } + + Super::EndPlay(EndPlayReason); + + /* Remove components */ + GetWorld()->GetTimerManager().ClearTimer(ResetTimerHandle); + if(Overlay->IsInViewport()) Overlay->RemoveFromViewport(); +} + +void ACalibratioActor::HandleClusterEvent(const FDisplayClusterClusterEventJson& Event) +{ + if(!Event.Category.Equals("Calibratio")) return; //Not our Business + + if (Event.Name == "CalibratioReset") + { + LocalReset(); + } + else if (Event.Name == "CalibratioSetThreshold" && Event.Parameters.Contains("NewThreshold")) + { + LocalSetThreshold(FCString::Atof(*Event.Parameters["NewThreshold"])); + } + else if (Event.Name == "CalibratioArmAndSetCalibration" && Event.Parameters.Contains("NewMin") && Event.Parameters.Contains("NewMax")) + { + LocalArmAndSetCalibration(FCString::Atof(*Event.Parameters["NewMin"]), FCString::Atof(*Event.Parameters["NewMax"])); + } + else if (Event.Name == "CalibratioDespawn") + { + LocalDespawn(); + } +} + +void ACalibratioActor::ClusterReset() +{ + if(UVirtualRealityUtilities::IsRoomMountedMode()) + { + IDisplayClusterClusterManager* const Manager = IDisplayCluster::Get().GetClusterMgr(); + if (!Manager) return; + + FDisplayClusterClusterEventJson ClusterEvent; + ClusterEvent.Name = "CalibratioReset"; + ClusterEvent.Category = "Calibratio"; + Manager->EmitClusterEventJson(ClusterEvent, true); + } + else if(UVirtualRealityUtilities::IsDesktopMode()) + { + LocalReset(); + } +} + +void ACalibratioActor::LocalReset() +{ + DynamicMaterial->SetVectorParameterValue("Color", FColor::Red); + CurrentCalibrationRuns = 0; + MaxRotation = -FLT_MAX; + MinRotation = FLT_MAX; + LastRotations.Empty(); + LastRotations.Reserve(MaxCalibrationRuns); +} + +void ACalibratioActor::ClusterIncreaseThreshold() +{ + ClusterChangeThreshold(FMath::DegreesToRadians(0.1f)); +} + +void ACalibratioActor::ClusterDecreaseThreshold() +{ + ClusterChangeThreshold(-FMath::DegreesToRadians(0.1f)); +} + +void ACalibratioActor::ClusterChangeThreshold(float Value) +{ + const float NewThreshold = Threshold + Value; + + if(UVirtualRealityUtilities::IsRoomMountedMode()) + { + IDisplayClusterClusterManager* const Manager = IDisplayCluster::Get().GetClusterMgr(); + if (!Manager) return; + + FDisplayClusterClusterEventJson ClusterEvent; + ClusterEvent.Name = "CalibratioSetThreshold"; + ClusterEvent.Category = "Calibratio"; + ClusterEvent.Parameters.Add("NewThreshold",FString::SanitizeFloat(NewThreshold)); + Manager->EmitClusterEventJson(ClusterEvent, true); + } + else if(UVirtualRealityUtilities::IsDesktopMode()) + { + LocalSetThreshold(NewThreshold); + } +} + +void ACalibratioActor::LocalSetThreshold(float NewValue) +{ + Threshold = NewValue; + Overlay->SetThresholds(MinRotation, MaxRotation, Threshold); +} + +void ACalibratioActor::ClusterArmAndSetCalibration(float MinAngle, float MaxAngle) +{ + if(UVirtualRealityUtilities::IsRoomMountedMode()) + { + IDisplayClusterClusterManager* const Manager = IDisplayCluster::Get().GetClusterMgr(); + if (!Manager) return; + + FDisplayClusterClusterEventJson ClusterEvent; + ClusterEvent.Name = "CalibratioArmAndSetCalibration"; + ClusterEvent.Category = "Calibratio"; + ClusterEvent.Parameters.Add("NewMin",FString::SanitizeFloat(MinAngle)); + ClusterEvent.Parameters.Add("NewMax",FString::SanitizeFloat(MaxAngle)); + Manager->EmitClusterEventJson(ClusterEvent, true); + } + else if(UVirtualRealityUtilities::IsDesktopMode()) + { + LocalArmAndSetCalibration(MinAngle, MaxAngle); + } +} + +void ACalibratioActor::LocalArmAndSetCalibration(float NewMin, float NewMax) +{ + MinRotation = NewMin; + MaxRotation = NewMax; + Overlay->SetStatus(Waiting); + DynamicMaterial->SetVectorParameterValue("Color", FColor::Black); + CurrentCalibrationRuns = MaxCalibrationRuns + 1; //Arm +} + +void ACalibratioActor::ClusterDespawn() +{ + if (UVirtualRealityUtilities::IsRoomMountedMode()) + { + IDisplayClusterClusterManager* const Manager = IDisplayCluster::Get().GetClusterMgr(); + if (!Manager) return; + + FDisplayClusterClusterEventJson ClusterEvent; + ClusterEvent.Name = "CalibratioDespawn"; + ClusterEvent.Category = "Calibratio"; + Manager->EmitClusterEventJson(ClusterEvent, true); + } + else if (UVirtualRealityUtilities::IsDesktopMode()) + { + LocalDespawn(); + } +} +void ACalibratioActor::LocalDespawn() +{ + GetWorld()->DestroyActor(this); // Destroy ourself +} + +void ACalibratioActor::Tick(const float DeltaSeconds) +{ + Super::Tick(DeltaSeconds); + + /* Search for tracker */ + if(!TrackedClusterComponent) + { + TrackedClusterComponent = UVirtualRealityUtilities::GetNamedClusterComponent(ENamedClusterComponent::NCC_CALIBRATIO); + } else + { + const FVector NewPos = TrackedClusterComponent->GetComponentLocation(); + if(NewPos != LastVisiblePosition) + { + if(FirstPositionSet) LastTimeVisible = FDateTime::Now(); + + LastVisiblePosition = NewPos; + FirstPositionSet = true; + } + } + + if (!IsTrackerCurrentlyVisible() || !TrackedClusterComponent) + { + Overlay->SetPhysicalStatus(NotFound); + Overlay->SetStatus(Waiting); + return; + } + + /* Tracker is visible */ + const float Rotation = TrackedClusterComponent->GetRelativeRotation().Yaw; + + /* First run, place mesh */ + if (CurrentCalibrationRuns == 0) + { + Mesh->SetRelativeLocation(TrackedClusterComponent->GetRelativeLocation()); + } + /* More calibration runs to go */ + if (CurrentCalibrationRuns < MaxCalibrationRuns) + { + Overlay->SetStatus(Calibrating); + if (IsDeviceMoving(Rotation)) + { + Overlay->SetPhysicalStatus(Moving); + LocalReset(); + } + else + { + Overlay->SetPhysicalStatus(Found); + CalibrationRun(Rotation); + } + } + /* Calibration finished */ + else if (CurrentCalibrationRuns == MaxCalibrationRuns) + { + ClusterArmAndSetCalibration(MinRotation, MaxRotation); /* Sync to other nodes */ + } + /* Actual Measuring */ + else if (Rotation < MinRotation - Threshold || Rotation > MaxRotation + Threshold) + { + if (!GetWorld()->GetTimerManager().IsTimerActive(ResetTimerHandle)) + { + DynamicMaterial->SetVectorParameterValue("Color", FColor::White); + Overlay->SetStatus(Triggered); + GetWorld()->GetTimerManager().SetTimer(ResetTimerHandle, this, &ACalibratioActor::ClusterReset, ResetTime, false); + } + } +} + +/* Checks if last 10 calls to this function handed in roughly the same Angle */ +bool ACalibratioActor::IsDeviceMoving(float Angle) +{ + LastRotations.Add(Angle); /* Push into back */ + if (LastRotations.Num() > 10) LastRotations.RemoveAt(0); /* Remove first one */ + + float Sum = 0.0f; + for (float& CurrentAngle : LastRotations) + { + Sum += CurrentAngle; + } + const float Average = Sum / LastRotations.Num(); + + return FMath::Abs(Average - Angle) > 5 * Threshold && LastRotations.Num() >= 10; +} + +/* Adjusts min and max rotation values */ +void ACalibratioActor::CalibrationRun(float Angle) +{ + if(!UVirtualRealityUtilities::IsMaster()) return; //Not our Business + + MinRotation = FMath::Min(MinRotation, Angle); + MaxRotation = FMath::Max(MaxRotation, Angle); + Overlay->SetThresholds(MinRotation, MaxRotation, Threshold); + + CurrentCalibrationRuns++; +} + +bool ACalibratioActor::IsTrackerCurrentlyVisible() +{ + return (FDateTime::Now() - LastTimeVisible).GetTotalMilliseconds() <= AcceptedAbscenceTime; +} diff --git a/Source/Calibratio/Private/CalibratioOverlay.cpp b/Source/Calibratio/Private/CalibratioOverlay.cpp new file mode 100644 index 0000000000000000000000000000000000000000..af91d38aa2403abd2c70739c3731027b5d65d42c --- /dev/null +++ b/Source/Calibratio/Private/CalibratioOverlay.cpp @@ -0,0 +1,60 @@ +#include "CalibratioOverlay.h" + +bool UCalibratioOverlay::Initialize() +{ + const bool Result = Super::Initialize(); + DismissButton->OnClicked.AddDynamic(this, &UCalibratioOverlay::Dismiss); + return Result; +} + +void UCalibratioOverlay::SetStatus(const ECalibratioProtocolStatus Status) +{ + if(CurrentStatus == Status) return; + switch(Status) + { + case Calibrating: + StatusProtocol->SetText(FText::FromString(TEXT("Calibrating"))); + break; + case Waiting: + StatusProtocol->SetText(FText::FromString(TEXT("Waiting"))); + break; + case Triggered: + StatusProtocol->SetText(FText::FromString(TEXT("Triggered"))); + break; + } + CurrentStatus = Status; +} + +void UCalibratioOverlay::SetThresholds(const float Min, const float Max, const float Threshold) +{ + MinAngle->SetText(FText::FromString(FString::Printf(TEXT("%.2f\u00B0"), FMath::Fmod(FMath::RadiansToDegrees(Min), 360)))); + MaxAngle->SetText(FText::FromString(FString::Printf(TEXT("%.2f\u00B0"), FMath::Fmod(FMath::RadiansToDegrees(Max), 360)))); + CurrentThreshold->SetText(FText::FromString(FString::Printf(TEXT("+-%.2f\u00B0s"), FMath::Fmod(FMath::RadiansToDegrees(Threshold), 360)))); +} + +void UCalibratioOverlay::SetPhysicalStatus(const ECalibratioPhysicalStatus Status) +{ + if(CurrentPhysicalStatus == Status) return; + switch(Status) + { + case Found: + StatusCalibratio->SetText(FText::FromString(TEXT("Found"))); + StatusCalibratio->SetColorAndOpacity(FSlateColor(FLinearColor::Green)); + break; + case Unknown: + case NotFound: + StatusCalibratio->SetText(FText::FromString(TEXT("Not Found"))); + StatusCalibratio->SetColorAndOpacity(FSlateColor(FLinearColor::Red)); + break; + case Moving: + StatusCalibratio->SetText(FText::FromString(TEXT("Moving"))); + StatusCalibratio->SetColorAndOpacity(FSlateColor(FLinearColor::Yellow)); + break; + } + CurrentPhysicalStatus = Status; +} + +void UCalibratioOverlay::Dismiss() +{ + Owner->ClusterDespawn(); +} diff --git a/Source/Calibratio/Public/Calibratio.h b/Source/Calibratio/Public/Calibratio.h new file mode 100644 index 0000000000000000000000000000000000000000..0acb193d56c0d4535840d100a5ad9d149289ee9a --- /dev/null +++ b/Source/Calibratio/Public/Calibratio.h @@ -0,0 +1,20 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" +#include "CalibratioActor.h" +#include "Cluster/IDisplayClusterClusterManager.h" + +class FCalibratioModule : public IModuleInterface +{ +public: + virtual void StartupModule () override; + virtual void ShutdownModule() override; + +public: + static void SpawnCalibratio(); + +private: + IConsoleCommand* CalibratioConsoleCommand = nullptr; + FOnClusterEventJsonListener ClusterEventListenerDelegate; +}; diff --git a/Source/Calibratio/Public/CalibratioActor.h b/Source/Calibratio/Public/CalibratioActor.h new file mode 100644 index 0000000000000000000000000000000000000000..a2bac0d157826ae3ff515032a5170a7704e40b08 --- /dev/null +++ b/Source/Calibratio/Public/CalibratioActor.h @@ -0,0 +1,70 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Materials/MaterialInstanceDynamic.h" +#include "Cluster/IDisplayClusterClusterManager.h" +#include "Components/DisplayClusterSceneComponent.h" + +#include "GameFramework/Actor.h" +#include "CalibratioActor.generated.h" + +UCLASS() +class CALIBRATIO_API ACalibratioActor : public AActor +{ + GENERATED_BODY() + +public: + ACalibratioActor(); + +protected: + virtual void BeginPlay() override; + virtual void PostInitializeComponents() override; + virtual void Tick(float DeltaSeconds) override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + +public: + UPROPERTY(VisibleAnywhere) UStaticMeshComponent* Mesh; + UPROPERTY(EditAnywhere) UMaterialInterface* BaseMaterial; + UPROPERTY(BlueprintReadWrite) float Threshold = FMath::DegreesToRadians(1.7f); + UPROPERTY(BlueprintReadWrite) float ResetTime = 0.5f; + UFUNCTION(Blueprintcallable) void ClusterDespawn(); + +private: + DECLARE_DELEGATE_OneParam(FThresholdDelegate, float); + FOnClusterEventJsonListener ClusterEventListenerDelegate; + + void CalibrationRun(float Angle); + bool IsTrackerCurrentlyVisible(); + void LocalSetThreshold(float NewValue); + void LocalArmAndSetCalibration(float NewMin, float NewMax); + void LocalDespawn(); + bool IsDeviceMoving(float Angle); + void LocalReset(); + + // Handling Cluster events + UFUNCTION() void ClusterReset(); + UFUNCTION() void ClusterIncreaseThreshold(); + UFUNCTION() void ClusterDecreaseThreshold(); + void ClusterChangeThreshold(float Value); + void ClusterArmAndSetCalibration(float MinAngle, float MaxAngle); + void HandleClusterEvent(const FDisplayClusterClusterEventJson& Event); + + float MinRotation{0}; + float MaxRotation{0}; + int CurrentCalibrationRuns{0}; + int MaxCalibrationRuns{60}; + FTimerHandle ResetTimerHandle; + TArray<float> LastRotations; + + UPROPERTY() UMaterialInstanceDynamic* DynamicMaterial = nullptr; + + FDateTime LastTimeVisible = FDateTime::MinValue(); + FVector LastVisiblePosition = FVector(NAN, NAN, NAN); + bool FirstPositionSet = false; + uint32 AcceptedAbscenceTime = 500u; // in Milliseconds + UPROPERTY() UDisplayClusterSceneComponent* TrackedClusterComponent = nullptr; + + //Overlay + TSubclassOf<class UCalibratioOverlay> Overlay_Class; + UPROPERTY() UCalibratioOverlay* Overlay; +}; diff --git a/Source/Calibratio/Public/CalibratioOverlay.h b/Source/Calibratio/Public/CalibratioOverlay.h new file mode 100644 index 0000000000000000000000000000000000000000..c7968074fd6fecf3bd3b4cf3ae57dd64982c978c --- /dev/null +++ b/Source/Calibratio/Public/CalibratioOverlay.h @@ -0,0 +1,78 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "CalibratioActor.h" +#include "Blueprint/UserWidget.h" +#include "Components/TextBlock.h" +#include "Components/Button.h" + +#include "CalibratioOverlay.generated.h" + + +UENUM(BlueprintType) +enum ECalibratioProtocolStatus +{ + Calibrating, + Waiting, + Triggered +}; + +UENUM(BlueprintType) +enum ECalibratioPhysicalStatus +{ + Moving, + Found, + NotFound, + Unknown +}; + +/** + * This is the parent class for the Overlay that is used on the master. + * All declarations in it are magically bound to the UMG child class if they are named the same (see "meta = (BindWidget)") + */ +UCLASS() +class CALIBRATIO_API UCalibratioOverlay : public UUserWidget +{ + GENERATED_BODY() + +virtual bool Initialize() override; +public: + /* Public Buttons */ + UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) + UButton* ResettingButton; + UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) + UButton* IncreaseThresholdButton; + UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) + UButton* DecreaseThresholdButton; + +protected: + UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) + UButton* DismissButton; + + /* Numbers: */ + UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) + UTextBlock* CurrentThreshold; + UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) + UTextBlock* MinAngle; + UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) + UTextBlock* MaxAngle; + + /* Status: */ + UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) + UTextBlock* StatusProtocol; + UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) + UTextBlock* StatusCalibratio; + +public: + UFUNCTION() void SetStatus(ECalibratioProtocolStatus Status); + UFUNCTION() void SetThresholds(float Min, float Max, float Threshold); + UFUNCTION() void SetPhysicalStatus(ECalibratioPhysicalStatus Status); + void SetOwner(ACalibratioActor* InOwner) {Owner = InOwner;} + UFUNCTION() void Dismiss(); + +private: + ECalibratioPhysicalStatus CurrentPhysicalStatus = Unknown; + ECalibratioProtocolStatus CurrentStatus = Calibrating; + UPROPERTY() ACalibratioActor* Owner; +}; diff --git a/Source/RWTHVRToolkit/Private/Cluster/CAVEOverlay/CAVEOverlayController.cpp b/Source/RWTHVRToolkit/Private/Cluster/CAVEOverlay/CAVEOverlayController.cpp index 71251d1765a7797731356eed2cc8fb2ae49bc7bf..96b53f9733aecd65ab685348cb3aa1d35412f1ad 100644 --- a/Source/RWTHVRToolkit/Private/Cluster/CAVEOverlay/CAVEOverlayController.cpp +++ b/Source/RWTHVRToolkit/Private/Cluster/CAVEOverlay/CAVEOverlayController.cpp @@ -33,15 +33,6 @@ UStaticMeshComponent* ACAVEOverlayController::CreateMeshComponent(const FName& N 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() { @@ -69,9 +60,9 @@ ACAVEOverlayController::ACAVEOverlayController() SignRoot->SetupAttachment(Root); //Loading of Materials and Meshes - LoadAsset("/RWTHVRToolkit/CAVEOverlay/Stripes", TapeMaterial); - LoadAsset("/RWTHVRToolkit/CAVEOverlay/StopMaterial", SignMaterial); - LoadAsset("/RWTHVRToolkit/CAVEOverlay/Plane", PlaneMesh); + UVirtualRealityUtilities::LoadAsset("/RWTHVRToolkit/CAVEOverlay/Stripes", TapeMaterial); + UVirtualRealityUtilities::LoadAsset("/RWTHVRToolkit/CAVEOverlay/StopMaterial", SignMaterial); + UVirtualRealityUtilities::LoadAsset("/RWTHVRToolkit/CAVEOverlay/Plane", PlaneMesh); TapeNegativeY = CreateMeshComponent("TapeNegY", PlaneMesh, TapeRoot); TapeNegativeX = CreateMeshComponent("TapeNegX", PlaneMesh, TapeRoot); diff --git a/Source/RWTHVRToolkit/Private/Cluster/ClusterConsole.cpp b/Source/RWTHVRToolkit/Private/Cluster/ClusterConsole.cpp index 7f4ecc4086c0664f1a7f11fbce5be6c47acf0566..966f4f01cd87adbb2318c49411c9b69b343cc9c0 100644 --- a/Source/RWTHVRToolkit/Private/Cluster/ClusterConsole.cpp +++ b/Source/RWTHVRToolkit/Private/Cluster/ClusterConsole.cpp @@ -8,7 +8,7 @@ void FClusterConsole::Register() 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) return; + if(IDisplayCluster::Get().GetClusterMgr() == nullptr || Args.Num() == 0) return; /* Emitting cluster event */ FDisplayClusterClusterEventJson ClusterEvent; diff --git a/Source/RWTHVRToolkit/Private/Fixes/ActivateConsoleInShipping.cpp b/Source/RWTHVRToolkit/Private/Fixes/ActivateConsoleInShipping.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9164ffbcbe5f86a906c972c9631c421e2e9ba863 --- /dev/null +++ b/Source/RWTHVRToolkit/Private/Fixes/ActivateConsoleInShipping.cpp @@ -0,0 +1,24 @@ +#include "Fixes/ActivateConsoleInShipping.h" +#include "Engine/Console.h" + +void FActivateConsoleInShipping::Register() +{ + if(FApp::GetBuildConfiguration() != EBuildConfiguration::Shipping) return; /* Should only enable console in shipping */ + + On_Post_World_Initialization_Delegate.BindRaw(this, &FActivateConsoleInShipping::OnSessionStart); + StartHandle = FWorldDelegates::OnPostWorldInitialization.Add(On_Post_World_Initialization_Delegate); +} + +void FActivateConsoleInShipping::Unregister() const +{ + if(FApp::GetBuildConfiguration() != EBuildConfiguration::Shipping) return; + + FWorldDelegates::OnPostWorldInitialization.Remove(StartHandle); +} + +void FActivateConsoleInShipping::OnSessionStart(UWorld* World, const UWorld::InitializationValues Values) const +{ + if(!World->IsGameWorld() || !World->GetGameViewport() || World->GetGameViewport()->ViewportConsole != nullptr) return; + + World->GetGameViewport()->ViewportConsole = NewObject<UConsole>(World->GetGameViewport()); +} diff --git a/Source/RWTHVRToolkit/Private/Fixes/FixNDisplayStereoDevice.cpp b/Source/RWTHVRToolkit/Private/Fixes/FixNDisplayStereoDevice.cpp deleted file mode 100644 index 3b946733acc7e188612b9e449773154db58b9993..0000000000000000000000000000000000000000 --- a/Source/RWTHVRToolkit/Private/Fixes/FixNDisplayStereoDevice.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "Fixes/FixNDisplayStereoDevice.h" - -void FFixNDisplayStereoDevice::Register() -{ - On_Post_World_Initialization_Delegate.BindRaw(this, &FFixNDisplayStereoDevice::OnSessionStart); - StartHandle = FWorldDelegates::OnPostWorldInitialization.Add(On_Post_World_Initialization_Delegate); - - On_Pre_World_Finish_Destroy_Delegate.BindRaw(this, &FFixNDisplayStereoDevice::OnSessionEnd); - EndHandle = FWorldDelegates::OnPreWorldFinishDestroy.Add(On_Pre_World_Finish_Destroy_Delegate); -} - -void FFixNDisplayStereoDevice::Unregister() -{ - FWorldDelegates::OnPostWorldInitialization.Remove(StartHandle); - FWorldDelegates::OnPreWorldFinishDestroy.Remove(EndHandle); -} - -void FFixNDisplayStereoDevice::OnSessionStart(UWorld*, const UWorld::InitializationValues) -{ - /* Store handle before it is released */ - if(GEngine) - { - StoredRenderingDevice = GEngine->StereoRenderingDevice; - } -} - -void FFixNDisplayStereoDevice::OnSessionEnd(UWorld*) -{ - /* Restore handle after it was released */ - if(GEngine) - { - GEngine->StereoRenderingDevice = StoredRenderingDevice; - } -} diff --git a/Source/RWTHVRToolkit/Private/RWTHVRToolkit.cpp b/Source/RWTHVRToolkit/Private/RWTHVRToolkit.cpp index 1b35313f6149bdfd35643d44764cb49f524d0c8d..bf76c57c0140c3239bac2d16e89ee351364e0849 100644 --- a/Source/RWTHVRToolkit/Private/RWTHVRToolkit.cpp +++ b/Source/RWTHVRToolkit/Private/RWTHVRToolkit.cpp @@ -5,14 +5,14 @@ void FRWTHVRToolkitModule::StartupModule () { ClusterConsole.Register(); - StereoDeviceFix.Register(); CAVEOverlay.Register(); + ConsoleActivation.Register(); } void FRWTHVRToolkitModule::ShutdownModule() { ClusterConsole.Unregister(); - StereoDeviceFix.Unregister(); CAVEOverlay.Unregister(); + ConsoleActivation.Unregister(); } #undef LOCTEXT_NAMESPACE diff --git a/Source/RWTHVRToolkit/Private/Utility/VirtualRealityUtilities.cpp b/Source/RWTHVRToolkit/Private/Utility/VirtualRealityUtilities.cpp index 418b350fa7ce3b798516a43195d75afcc189d45b..2260a1e7261a3addb792a7d531b49da615db6af5 100644 --- a/Source/RWTHVRToolkit/Private/Utility/VirtualRealityUtilities.cpp +++ b/Source/RWTHVRToolkit/Private/Utility/VirtualRealityUtilities.cpp @@ -104,7 +104,8 @@ FString UVirtualRealityUtilities::GetNodeName() float UVirtualRealityUtilities::GetEyeDistance() { #if PLATFORM_SUPPORTS_NDISPLAY - return IDisplayCluster::Get().GetGameMgr()->GetRootActor()->GetDefaultCamera()->GetInterpupillaryDistance(); + ADisplayClusterRootActor* RootActor = IDisplayCluster::Get().GetGameMgr()->GetRootActor(); + return (RootActor) ? RootActor->GetDefaultCamera()->GetInterpupillaryDistance() : 0; #else return 0; #endif @@ -113,7 +114,8 @@ float UVirtualRealityUtilities::GetEyeDistance() EEyeStereoOffset UVirtualRealityUtilities::GetNodeEyeType() { #if PLATFORM_SUPPORTS_NDISPLAY - return static_cast<EEyeStereoOffset>(IDisplayCluster::Get().GetGameMgr()->GetRootActor()->GetDefaultCamera()->GetStereoOffset()); + ADisplayClusterRootActor* RootActor = IDisplayCluster::Get().GetGameMgr()->GetRootActor(); + return (RootActor) ? RootActor->GetDefaultCamera()->GetStereoOffset() : EDisplayClusterEyeStereoOffset::None; #else return None; #endif @@ -122,7 +124,8 @@ EEyeStereoOffset UVirtualRealityUtilities::GetNodeEyeType() USceneComponent* UVirtualRealityUtilities::GetClusterComponent(const FString& Name) { #if PLATFORM_SUPPORTS_NDISPLAY - return IDisplayCluster::Get().GetGameMgr()->GetRootActor()->GetComponentById(Name); + ADisplayClusterRootActor* RootActor = IDisplayCluster::Get().GetGameMgr()->GetRootActor(); + return (RootActor) ? RootActor->GetComponentById(Name) : nullptr; #else return nullptr; #endif @@ -141,6 +144,7 @@ USceneComponent* UVirtualRealityUtilities::GetNamedClusterComponent(const ENamed case ENamedClusterComponent::NCC_FLYSTICK: return GetClusterComponent("flystick"); case ENamedClusterComponent::NCC_TDW_ORIGIN: return GetClusterComponent("tdw_origin_floor"); case ENamedClusterComponent::NCC_TDW_CENTER: return GetClusterComponent("tdw_center"); + case ENamedClusterComponent::NCC_CALIBRATIO: return GetClusterComponent("calibratio"); case ENamedClusterComponent::NCC_TRACKING_ORIGIN: USceneComponent* Result; if((Result = GetClusterComponent("cave_origin"))) return Result; @@ -150,4 +154,3 @@ USceneComponent* UVirtualRealityUtilities::GetNamedClusterComponent(const ENamed default: return nullptr; } } - diff --git a/Source/RWTHVRToolkit/Public/Fixes/ActivateConsoleInShipping.h b/Source/RWTHVRToolkit/Public/Fixes/ActivateConsoleInShipping.h new file mode 100644 index 0000000000000000000000000000000000000000..ae67362545450edba46a852c9d318cf1cf073c19 --- /dev/null +++ b/Source/RWTHVRToolkit/Public/Fixes/ActivateConsoleInShipping.h @@ -0,0 +1,24 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Engine/World.h" +#include "ActivateConsoleInShipping.generated.h" + +/** + * This fixes activates the debugging console shipping builds. Unfortunately, it can't activate the auto-completion inside the console + */ +USTRUCT() +struct RWTHVRTOOLKIT_API FActivateConsoleInShipping +{ + GENERATED_BODY() + + void Register(); + void Unregister() const; + +private: + void OnSessionStart(UWorld*, const UWorld::InitializationValues) const; + + TDelegate<void (UWorld*, const UWorld::InitializationValues)> On_Post_World_Initialization_Delegate; + + FDelegateHandle StartHandle; +}; diff --git a/Source/RWTHVRToolkit/Public/Fixes/FixNDisplayStereoDevice.h b/Source/RWTHVRToolkit/Public/Fixes/FixNDisplayStereoDevice.h deleted file mode 100644 index 81761f9df315466baccd090dfca962fc7ad903d4..0000000000000000000000000000000000000000 --- a/Source/RWTHVRToolkit/Public/Fixes/FixNDisplayStereoDevice.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "Engine/World.h" -#include "FixNDisplayStereoDevice.generated.h" - -/** - * This fixes the behavior of nDisplay DisplayClusterRenderManager, which resets the global stereo render device - * This is problematic, since it resets the pointer (L128) to the StereoRenderDevice, even if it does not re-initialize (L113) it. - * This results in a fine first start, but crashes on the second VR start. For Desktop this seems to still function fine. - * - * For this behavior a Pull-Request is in progress. See https://github.com/EpicGames/UnrealEngine/pull/7738 - */ -USTRUCT() -struct RWTHVRTOOLKIT_API FFixNDisplayStereoDevice -{ - GENERATED_BODY() - - void Register(); - void Unregister(); - -private: - void OnSessionStart(UWorld*, const UWorld::InitializationValues); - void OnSessionEnd(UWorld*); - - TSharedPtr< class IStereoRendering, ESPMode::ThreadSafe > StoredRenderingDevice; - - TDelegate<void (UWorld*, const UWorld::InitializationValues)> On_Post_World_Initialization_Delegate; - TDelegate<void (UWorld*)> On_Pre_World_Finish_Destroy_Delegate; - - FDelegateHandle StartHandle; - FDelegateHandle EndHandle; -}; diff --git a/Source/RWTHVRToolkit/Public/RWTHVRToolkit.h b/Source/RWTHVRToolkit/Public/RWTHVRToolkit.h index 3e0da836a6f873c48c5e4a8c3ef5475e8c0e352f..eb0b6662ae73da395fcd4c433b6ad601ca0c8ba1 100644 --- a/Source/RWTHVRToolkit/Public/RWTHVRToolkit.h +++ b/Source/RWTHVRToolkit/Public/RWTHVRToolkit.h @@ -2,9 +2,9 @@ #include "CoreMinimal.h" #include "Cluster/CAVEOverlay/CAVEOverlay.h" -#include "Fixes/FixNDisplayStereoDevice.h" #include "Modules/ModuleManager.h" #include "Cluster/ClusterConsole.h" +#include "Fixes/ActivateConsoleInShipping.h" class FRWTHVRToolkitModule : public IModuleInterface @@ -15,6 +15,6 @@ public: private: FClusterConsole ClusterConsole; - FFixNDisplayStereoDevice StereoDeviceFix; FCAVEOverlay CAVEOverlay; + FActivateConsoleInShipping ConsoleActivation; }; diff --git a/Source/RWTHVRToolkit/Public/Utility/VirtualRealityUtilities.h b/Source/RWTHVRToolkit/Public/Utility/VirtualRealityUtilities.h index d96d066b0045f4909a1c17c44969455728601369..ac1d18728792013b9c759fdefe3582ed12bad7c3 100644 --- a/Source/RWTHVRToolkit/Public/Utility/VirtualRealityUtilities.h +++ b/Source/RWTHVRToolkit/Public/Utility/VirtualRealityUtilities.h @@ -21,6 +21,7 @@ enum class ENamedClusterComponent : uint8 NCC_TDW_CENTER UMETA(DisplayName = "TDW Center"), /* Non Specific */ + NCC_CALIBRATIO UMETA(DisplayName = "Calibratio Motion to Photon Measurement Device"), NCC_SHUTTERGLASSES UMETA(DisplayName = "CAVE/ROLV/TDW Shutter Glasses"), NCC_FLYSTICK UMETA(DisplayName = "CAVE/ROLV/TDW Flystick"), NCC_TRACKING_ORIGIN UMETA(DisplayName = "CAVE/ROLV/TDW Origin") @@ -55,7 +56,33 @@ public: UFUNCTION(BlueprintPure, Category = "DisplayCluster") static EEyeStereoOffset GetNodeEyeType(); - //Get Compenent of Display Cluster by it's name, which is specified in the nDisplay config + //Get Component of Display Cluster by it's name, which is specified in the nDisplay config UFUNCTION(BlueprintPure, BlueprintCallable, Category = "DisplayCluster") static USceneComponent* GetClusterComponent(const FString& Name); UFUNCTION(BlueprintPure, BlueprintCallable, Category = "DisplayCluster") static USceneComponent* GetNamedClusterComponent(const ENamedClusterComponent& Component); + + /* Load and create an Object from an asset path. This only works in the constructor */ + template <class T> + static bool LoadAsset(const FString& Path, T*& Result); + + /* Finds and returns a class of an asset. This only works in the constructor */ + template <class T> + static bool LoadClass(const FString& Path, TSubclassOf<T>& Result); }; + +template <typename T> +bool UVirtualRealityUtilities::LoadAsset(const FString& Path, T* & Result) +{ + ConstructorHelpers::FObjectFinder<T> Loader(*Path); + Result = Loader.Object; + if (!Loader.Succeeded()) UE_LOG(LogTemp, Error, TEXT("Could not find %s. Have you renamed it?"), *Path); + return Loader.Succeeded(); +} + +template <typename T> +bool UVirtualRealityUtilities::LoadClass(const FString& Path, TSubclassOf<T> & Result) +{ + ConstructorHelpers::FClassFinder<T> Loader(*Path); + Result = Loader.Class; + if (!Loader.Succeeded()) UE_LOG(LogTemp, Error, TEXT("Could not find %s. Have you renamed it?"), *Path); + return Loader.Succeeded(); +} \ No newline at end of file diff --git a/Source/RWTHVRToolkitEditor/Private/RWTHVRToolkitEditor.cpp b/Source/RWTHVRToolkitEditor/Private/RWTHVRToolkitEditor.cpp index 22e614956f4810a3d4326318c5ac211d677ef861..ca8f91aab9430ac8b1617d1b1c365ad1d045f3d5 100644 --- a/Source/RWTHVRToolkitEditor/Private/RWTHVRToolkitEditor.cpp +++ b/Source/RWTHVRToolkitEditor/Private/RWTHVRToolkitEditor.cpp @@ -12,7 +12,7 @@ IMPLEMENT_GAME_MODULE(FRWTHVRToolkitEditorModule, RWTHVRToolkitEditor); -#define LOCTEXT_NAMESPACE "RWTHVRToolkitEdito" +#define LOCTEXT_NAMESPACE "RWTHVRToolkitEditor" void FRWTHVRToolkitEditorModule::StartupModule() {