diff --git a/RWTHVRCluster.uplugin b/RWTHVRCluster.uplugin index 0297d365bc0735c759ccfc11f0aa5bb4763315e9..d5a51b05b2bf52b34d77c81db889a656220d5447 100644 --- a/RWTHVRCluster.uplugin +++ b/RWTHVRCluster.uplugin @@ -14,6 +14,10 @@ "IsBetaVersion": false, "Installed": false, "EnabledByDefault": true, + "SupportedTargetPlatforms": [ + "Win64", + "Linux" + ], "Modules": [ { "Name": "RWTHVRCluster", @@ -26,10 +30,6 @@ } ], "Plugins": [ - { - "Name": "RWTHVRToolkit", - "Enabled": true - }, { "Name": "nDisplay", "Enabled": true, diff --git a/Source/RWTHVRCluster/Private/CAVEOverlay/CAVEOverlayController.cpp b/Source/RWTHVRCluster/Private/CAVEOverlay/CAVEOverlayController.cpp index 08d44b21a4e33283daa9a6a29b556beec0289cfa..4f27161686e7c6d20ca1b847da89a875e3c42e9c 100644 --- a/Source/RWTHVRCluster/Private/CAVEOverlay/CAVEOverlayController.cpp +++ b/Source/RWTHVRCluster/Private/CAVEOverlay/CAVEOverlayController.cpp @@ -4,16 +4,15 @@ #include "EnhancedInputComponent.h" #include "IDisplayCluster.h" #include "MotionControllerComponent.h" -#include "Camera/CameraComponent.h" #include "CAVEOverlay/DoorOverlayData.h" +#include "Camera/CameraComponent.h" #include "Cluster/DisplayClusterClusterEvent.h" #include "Cluster/IDisplayClusterClusterManager.h" #include "Components/StaticMeshComponent.h" #include "Engine/CollisionProfile.h" #include "Logging/StructuredLog.h" #include "Materials/MaterialInstanceDynamic.h" -#include "Pawn/RWTHVRPawn.h" -#include "Utility/RWTHVRUtilities.h" +#include "Utility/RWTHVRClusterUtilities.h" DEFINE_LOG_CATEGORY(LogCAVEOverlay); @@ -50,8 +49,8 @@ ACAVEOverlayController::ACAVEOverlayController() SetRootComponent(Root); Tape = CreateMeshComponent("Tape", Root); - SignRightHand = CreateMeshComponent("SignRightHand", Root); - SignLeftHand = CreateMeshComponent("SignLeftHand", Root); + SignsStaticMeshComponents.Reserve(2); + MotionControllers.Reserve(2); } void ACAVEOverlayController::CycleDoorType() @@ -158,11 +157,11 @@ void ACAVEOverlayController::BeginPlay() // Not sure which place would be best... const bool bValidPC = PC && PC->GetLocalPlayer(); - if (!bValidPC || !URWTHVRUtilities::IsRoomMountedMode()) + if (!bValidPC || !URWTHVRClusterUtilities::IsRoomMountedMode()) return; // Input config - if (URWTHVRUtilities::IsPrimaryNode()) + if (URWTHVRClusterUtilities::IsPrimaryNode()) { if (CycleDoorTypeInputAction == nullptr) { @@ -219,9 +218,35 @@ void ACAVEOverlayController::BeginPlay() Overlay->CornerText->SetText(FText::FromString("")); // Get the pawn so we can have access to head and hand positions - VRPawn = Cast<ARWTHVRPawn>(PC->GetPawnOrSpectator()); - if (VRPawn) + if (const auto* VRPawn = Cast<APawn>(PC->GetPawnOrSpectator())) { + PawnCamera = VRPawn->GetComponentByClass<UCameraComponent>(); + auto FoundMotionControllers = VRPawn->K2_GetComponentsByClass(UMotionControllerComponent::StaticClass()); + + for (const auto FoundMotionController : FoundMotionControllers) + { + if (auto* MC = Cast<UMotionControllerComponent>(FoundMotionController); + MC && MC->MotionSource != EName::None) + { + // Create new static mesh for them + auto* SignStaticMeshComp = NewObject<UStaticMeshComponent>(); + SignStaticMeshComp->SetStaticMesh(SignStaticMesh); + SignStaticMeshComp->SetupAttachment(RootComponent); + SignStaticMeshComp->RegisterComponent(); + + MotionControllers.Add(MC); + SignsStaticMeshComponents.Add(SignStaticMeshComp); + SignsMIDs.Add(SignStaticMeshComp->CreateAndSetMaterialInstanceDynamic(0)); + } + } + + if (MotionControllers.Num() != 2) + { + UE_LOGFMT(LogCAVEOverlay, Display, + "Found unexpected number of MotionControllers on Pawn. Expected 2, found {Number}. This might " + "lead to weird results", + MotionControllers.Num()); + } // we're good to go! bInitialized = true; } @@ -230,10 +255,8 @@ void ACAVEOverlayController::BeginPlay() UE_LOGFMT(LogCAVEOverlay, Error, "No VirtualRealityPawn found which we could attach to!"); } - // Create dynamic materials at runtime + // Create dynamic material for tape TapeMaterialDynamic = Tape->CreateDynamicMaterialInstance(0); - RightSignMaterialDynamic = SignRightHand->CreateDynamicMaterialInstance(0); - LeftSignMaterialDynamic = SignLeftHand->CreateDynamicMaterialInstance(0); UE_LOGFMT(LogCAVEOverlay, Display, "CaveOverlay Initialization was successfull."); } @@ -317,41 +340,46 @@ void ACAVEOverlayController::Tick(float DeltaTime) } // Head/Tape Logic - const FVector HeadPosition = VRPawn->HeadCameraComponent->GetRelativeTransform().GetLocation(); - const bool bHeadIsCloseToWall = - FMath::IsWithinInclusive(HeadPosition.GetAbsMax(), WallDistance - WallCloseDistance, WallDistance); - // Only show the tape when close to a wall and not within the door opening - if (bHeadIsCloseToWall && !PositionInDoorOpening(HeadPosition)) + if (PawnCamera) { - Tape->SetVisibility(true); + const FVector HeadPosition = PawnCamera->GetRelativeTransform().GetLocation(); + const bool bHeadIsCloseToWall = + FMath::IsWithinInclusive(HeadPosition.GetAbsMax(), WallDistance - WallCloseDistance, WallDistance); - // Offset the tape in z direction to always be at head height - Tape->SetRelativeLocation(HeadPosition * FVector(0, 0, 1)); - - TapeMaterialDynamic->SetScalarParameterValue("BarrierOpacity", CalculateOpacityFromPosition(HeadPosition)); - - if (FMath::IsWithin(FVector2D(HeadPosition).GetAbsMax(), WallDistance - WallWarningDistance, WallDistance)) + // Only show the tape when close to a wall and not within the door opening + if (bHeadIsCloseToWall && !PositionInDoorOpening(HeadPosition)) { - // in warning distance == red tape - TapeMaterialDynamic->SetVectorParameterValue("StripeColor", FVector(1, 0, 0)); + Tape->SetVisibility(true); + + // Offset the tape in z direction to always be at head height + Tape->SetRelativeLocation(HeadPosition * FVector(0, 0, 1)); + + TapeMaterialDynamic->SetScalarParameterValue("BarrierOpacity", CalculateOpacityFromPosition(HeadPosition)); + + if (FMath::IsWithin(FVector2D(HeadPosition).GetAbsMax(), WallDistance - WallWarningDistance, WallDistance)) + { + // in warning distance == red tape + TapeMaterialDynamic->SetVectorParameterValue("StripeColor", FVector(1, 0, 0)); + } + else + { + // otherwise we're just yellow + TapeMaterialDynamic->SetVectorParameterValue("StripeColor", FVector(1, 1, 0)); + } } else { - // otherwise we're just yellow - TapeMaterialDynamic->SetVectorParameterValue("StripeColor", FVector(1, 1, 0)); + Tape->SetVisibility(false); } } - else - { - Tape->SetVisibility(false); - } // Hand Logic - const FVector RightPosition = VRPawn->RightHand->GetRelativeTransform().GetLocation(); - const FVector LeftPosition = VRPawn->LeftHand->GetRelativeTransform().GetLocation(); + for (int i = 0; i < MotionControllers.Num(); i++) + { + const FVector HandPosition = MotionControllers[i]->GetRelativeLocation(); - // Set the position rotation, opacity, visibility of the hand warning signs. - SetSignsForHand(SignRightHand, RightPosition, RightSignMaterialDynamic); - SetSignsForHand(SignLeftHand, LeftPosition, LeftSignMaterialDynamic); + // Set the position rotation, opacity, visibility of the hand warning signs. + SetSignsForHand(SignsStaticMeshComponents[i], HandPosition, SignsMIDs[i]); + } } diff --git a/Source/RWTHVRCluster/Private/CaveSetup.cpp b/Source/RWTHVRCluster/Private/CaveSetup.cpp index c39e47d4997863ee26b9fe5065c327efc6aa669f..63456d4309d4af0b21899b3e72431add8facae70 100644 --- a/Source/RWTHVRCluster/Private/CaveSetup.cpp +++ b/Source/RWTHVRCluster/Private/CaveSetup.cpp @@ -4,8 +4,7 @@ #include "CaveSetup.h" #include "Logging/StructuredLog.h" -#include "Utility/RWTHVRUtilities.h" - +#include "Utility/RWTHVRClusterUtilities.h" // Sets default values ACaveSetup::ACaveSetup() @@ -22,7 +21,7 @@ void ACaveSetup::BeginPlay() { Super::BeginPlay(); - if (!URWTHVRUtilities::IsRoomMountedMode()) + if (!URWTHVRClusterUtilities::IsRoomMountedMode()) { return; } @@ -41,7 +40,7 @@ void ACaveSetup::BeginPlay() // Apply the DTrack LiveLink Preset. Only do this if we are the primaryNode - if (URWTHVRUtilities::IsPrimaryNode()) + if (URWTHVRClusterUtilities::IsPrimaryNode()) { if (LiveLinkPresetToApplyOnCave && LiveLinkPresetToApplyOnCave->IsValidLowLevelFast()) { diff --git a/Source/RWTHVRCluster/Private/Utility/RWTHVRClusterUtilities.cpp b/Source/RWTHVRCluster/Private/Utility/RWTHVRClusterUtilities.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f1f2a4cdb160a8ff0f543659602b54a1a1dba71a --- /dev/null +++ b/Source/RWTHVRCluster/Private/Utility/RWTHVRClusterUtilities.cpp @@ -0,0 +1,112 @@ +#include "Utility/RWTHVRClusterUtilities.h" + +#include "DisplayClusterConfigurationTypes.h" +#include "DisplayClusterRootActor.h" +#include "IDisplayCluster.h" +#include "Cluster/IDisplayClusterClusterManager.h" +#include "Components/DisplayClusterCameraComponent.h" +#include "Config/IDisplayClusterConfigManager.h" +#include "Engine/Engine.h" +#include "Engine/LocalPlayer.h" +#include "Game/IDisplayClusterGameManager.h" + +DEFINE_LOG_CATEGORY(ClusterPlugin); + +bool URWTHVRClusterUtilities::IsRoomMountedMode() +{ + return IDisplayCluster::Get().GetOperationMode() == EDisplayClusterOperationMode::Cluster; +} + +bool URWTHVRClusterUtilities::IsCave() +{ + if (!IsRoomMountedMode()) + return false; + + const UDisplayClusterConfigurationData* ClusterConfig = IDisplayCluster::Get().GetConfigMgr()->GetConfig(); + return ClusterConfig->CustomParameters.Contains("Hardware_Platform") && + ClusterConfig->CustomParameters.Find("Hardware_Platform")->Equals("aixcave", ESearchCase::IgnoreCase); +} + +bool URWTHVRClusterUtilities::IsRolv() +{ + if (!IsRoomMountedMode()) + return false; + + const UDisplayClusterConfigurationData* ClusterConfig = IDisplayCluster::Get().GetConfigMgr()->GetConfig(); + return ClusterConfig->CustomParameters.Contains("Hardware_Platform") && + ClusterConfig->CustomParameters.Find("Hardware_Platform")->Equals("ROLV", ESearchCase::IgnoreCase); +} + +/* Return true on the Primary in cluster mode and in a normal desktop session. Otherwise false */ +bool URWTHVRClusterUtilities::IsPrimaryNode() +{ + if (!IDisplayCluster::IsAvailable()) + { + return true; + } + IDisplayClusterClusterManager* Manager = IDisplayCluster::Get().GetClusterMgr(); + if (Manager == nullptr) + { + return true; // if we are not in cluster mode, we are always the primary node + } + return Manager->IsPrimary() || !Manager->IsSecondary(); +} + +bool URWTHVRClusterUtilities::IsSecondaryNode() { return !IsPrimaryNode(); } + +FString URWTHVRClusterUtilities::GetNodeName() +{ + return IsRoomMountedMode() ? IDisplayCluster::Get().GetClusterMgr()->GetNodeId() : FString(TEXT("Localhost")); +} + +float URWTHVRClusterUtilities::GetEyeDistance() +{ + const ADisplayClusterRootActor* RootActor = IDisplayCluster::Get().GetGameMgr()->GetRootActor(); + return (RootActor) ? RootActor->GetDefaultCamera()->GetInterpupillaryDistance() : 0.0f; +} + +EDisplayClusterEyeStereoOffset URWTHVRClusterUtilities::GetNodeEyeType() +{ + const ADisplayClusterRootActor* RootActor = IDisplayCluster::Get().GetGameMgr()->GetRootActor(); + return (RootActor) ? RootActor->GetDefaultCamera()->GetStereoOffset() : EDisplayClusterEyeStereoOffset::None; +} + +USceneComponent* URWTHVRClusterUtilities::GetClusterComponent(const FString& Name) +{ + const ADisplayClusterRootActor* RootActor = IDisplayCluster::Get().GetGameMgr()->GetRootActor(); + return (RootActor) ? RootActor->GetComponentByName<USceneComponent>(Name) : nullptr; +} + +USceneComponent* URWTHVRClusterUtilities::GetNamedClusterComponent(const ENamedClusterComponent& Component) +{ + switch (Component) + { + case ENamedClusterComponent::NCC_CAVE_ORIGIN: + return GetClusterComponent("cave_origin"); + case ENamedClusterComponent::NCC_CAVE_CENTER: + return GetClusterComponent("cave_center"); + case ENamedClusterComponent::NCC_CAVE_LHT: + return GetClusterComponent("left_hand_target"); + case ENamedClusterComponent::NCC_CAVE_RHT: + return GetClusterComponent("right_hand_target"); + case ENamedClusterComponent::NCC_SHUTTERGLASSES: + return GetClusterComponent("shutter_glasses"); + case ENamedClusterComponent::NCC_ROLV_ORIGIN: + return GetClusterComponent("rolv_origin"); + case ENamedClusterComponent::NCC_FLYSTICK: + return GetClusterComponent("flystick"); + case ENamedClusterComponent::NCC_CALIBRATIO: + return GetClusterComponent("calibratio"); + case ENamedClusterComponent::NCC_TRACKING_ORIGIN: + USceneComponent* Result; + if ((Result = GetClusterComponent("cave_origin"))) + return Result; + if ((Result = GetClusterComponent("rolv_origin"))) + return Result; + if ((Result = GetClusterComponent("tdw_origin_floor"))) + return Result; + return nullptr; + default: + return nullptr; + } +} diff --git a/Source/RWTHVRCluster/Public/CAVEOverlay/CAVEOverlayController.h b/Source/RWTHVRCluster/Public/CAVEOverlay/CAVEOverlayController.h index 7740522090059295ef11a97762c35013edeaa3de..6d9828eab107bb37d48fb2d0d015c434f1077646 100644 --- a/Source/RWTHVRCluster/Public/CAVEOverlay/CAVEOverlayController.h +++ b/Source/RWTHVRCluster/Public/CAVEOverlay/CAVEOverlayController.h @@ -8,7 +8,10 @@ #include "CAVEOverlayController.generated.h" +class UMotionControllerComponent; +class UCameraComponent; class ARWTHVRPawn; + DECLARE_LOG_CATEGORY_EXTERN(LogCAVEOverlay, Log, All); /** @@ -91,9 +94,12 @@ private: // Only calculate positions and material values when we're fully initialized. bool bInitialized = false; - // Reference to the currently active pawn that we're tracking positions of. + // Reference to the currently active pawn's camera and hands that we're tracking positions of. + UPROPERTY() + UCameraComponent* PawnCamera; + UPROPERTY() - ARWTHVRPawn* VRPawn; + TArray<UMotionControllerComponent*> MotionControllers; // Cluster Events FOnClusterEventJsonListener ClusterEventListenerDelegate; @@ -114,13 +120,17 @@ public: UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true")) UStaticMeshComponent* Tape; - // Right Hand Sign Static Mesh component. Reference to static mesh needs to be set in the corresponding BP. + // Right Hand Sign Static Mesh Reference UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true")) - UStaticMeshComponent* SignRightHand; + UStaticMesh* SignStaticMesh; - // Left Hand Sign Static Mesh component. Reference to static mesh needs to be set in the corresponding BP. + // Static Mesh Components for all tracked MotionControllers UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true")) - UStaticMeshComponent* SignLeftHand; + TArray<UStaticMeshComponent*> SignsStaticMeshComponents; + + // Static Mesh Components for all tracked MotionControllers + UPROPERTY() + TArray<UMaterialInstanceDynamic*> SignsMIDs; // UI Overlay UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "CAVEOverlay") @@ -136,10 +146,4 @@ public: // Dynamic Materials to control opacity UPROPERTY() UMaterialInstanceDynamic* TapeMaterialDynamic; - - UPROPERTY() - UMaterialInstanceDynamic* RightSignMaterialDynamic; - - UPROPERTY() - UMaterialInstanceDynamic* LeftSignMaterialDynamic; }; diff --git a/Source/RWTHVRCluster/Public/Utility/RWTHVRClusterUtilities.h b/Source/RWTHVRCluster/Public/Utility/RWTHVRClusterUtilities.h new file mode 100644 index 0000000000000000000000000000000000000000..cf2ad5a9a18b02a96d6747af552aa6b20b1b58c9 --- /dev/null +++ b/Source/RWTHVRCluster/Public/Utility/RWTHVRClusterUtilities.h @@ -0,0 +1,69 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Components/DisplayClusterCameraComponent.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "UObject/ConstructorHelpers.h" + +#include "RWTHVRClusterUtilities.generated.h" + + +/** + * Custom log category for all ClusterPlugin related components + */ +DECLARE_LOG_CATEGORY_EXTERN(ClusterPlugin, Log, All); + +UENUM(BlueprintType) +enum class ENamedClusterComponent : uint8 +{ + /* CAVE Specific */ + NCC_CAVE_ORIGIN UMETA(DisplayName = "CAVE Origin"), + NCC_CAVE_CENTER UMETA(DisplayName = "CAVE Center"), + NCC_CAVE_LHT UMETA(DisplayName = "CAVE Left Hand Target"), + NCC_CAVE_RHT UMETA(DisplayName = "CAVE Right Hand Target"), + + /* ROLV Specific */ + NCC_ROLV_ORIGIN UMETA(DisplayName = "ROLV Origin"), + + /* 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") +}; + +UCLASS() +class RWTHVRCLUSTER_API URWTHVRClusterUtilities : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + static bool IsRoomMountedMode(); + UFUNCTION(BlueprintPure, Category = "DisplayCluster|Platform") + static bool IsCave(); + UFUNCTION(BlueprintPure, Category = "DisplayCluster|Platform") + static bool IsRolv(); + + UFUNCTION(BlueprintPure, Category = "DisplayCluster") + static bool IsPrimaryNode(); + UFUNCTION(BlueprintPure, Category = "DisplayCluster") + static bool IsSecondaryNode(); + + UFUNCTION(BlueprintPure, Category = "DisplayCluster") + static FString GetNodeName(); + /* Distance in meters */ + UFUNCTION(BlueprintPure, Category = "DisplayCluster") + static float GetEyeDistance(); + + UFUNCTION(BlueprintPure, Category = "DisplayCluster") + static EDisplayClusterEyeStereoOffset GetNodeEyeType(); + + //Get Component of Display Cluster by it's name, which is specified in the nDisplay config + UE_DEPRECATED(5.4, "GetClusterComponent has been removed because it is obsolete.") + UFUNCTION(BlueprintPure, BlueprintCallable, Category = "DisplayCluster", meta = (DeprecatedFunction)) + static USceneComponent* GetClusterComponent(const FString& Name); + + UE_DEPRECATED(5.4, "GetNamedClusterComponent has been removed because it is obsolete.") + UFUNCTION(BlueprintPure, BlueprintCallable, Category = "DisplayCluster", meta = (DeprecatedFunction)) + static USceneComponent* GetNamedClusterComponent(const ENamedClusterComponent& Component); +}; diff --git a/Source/RWTHVRCluster/RWTHVRCluster.Build.cs b/Source/RWTHVRCluster/RWTHVRCluster.Build.cs index 12b3d9afcead6504a5624ff5ec0bdb031849f531..98f6200330450e0a35572838fe11bc02a88997e3 100644 --- a/Source/RWTHVRCluster/RWTHVRCluster.Build.cs +++ b/Source/RWTHVRCluster/RWTHVRCluster.Build.cs @@ -26,8 +26,8 @@ public class RWTHVRCluster : ModuleRules "Slate", "SlateCore", "LiveLink", - "DisplayCluster", - "RWTHVRToolkit" + "HeadMountedDisplay", // required for MotionControllerComp + "DisplayCluster" } );