You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Fixes several bugs sharing the same root cause, where code/blueprints were attempting to branch on UVREditorMode::GetHMDDeviceType() or UVREditorInteractor::GetHMDDeviceType(). These calls previously returned the result of IXRTrackingSystem::GetSystemName(), and the call sites were only set up to handle "OculusHMD" or "SteamVR," and were now seeing "OpenXR". Instead, we now return one of those legacy plugin names, depending on the current OpenXR hand controller interaction profile. Longer term, we will want to revisit each of these decision points individually, and take into account support for future unknown XR devices. An alternative subclass of UVREditorMode can now be configured in the editor settings alongside the interactor and teleporter classes, and a new UVirtualScoutingMode subclass splits the mode entry into two phases. This is necessary because OpenXR may not return the correct interaction profile for several frames after the OpenXR session / stereo rendering has started, and we need to defer creation of the interactors, etc. #jira UE-150037 #rb jason.walter, Lauren.Barnes #preflight 6287f49a6c7692ac8cd00f29 [CL 20302573 by zach brockway in ue5-main branch]
1238 lines
45 KiB
C++
1238 lines
45 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "VREditorMode.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "UObject/ConstructorHelpers.h"
|
|
#include "Widgets/Docking/SDockTab.h"
|
|
#include "Engine/EngineTypes.h"
|
|
#include "Components/SceneComponent.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "GameFramework/Actor.h"
|
|
#include "Engine/World.h"
|
|
#include "Components/SpotLightComponent.h"
|
|
#include "GameFramework/WorldSettings.h"
|
|
#include "DrawDebugHelpers.h"
|
|
#include "Materials/MaterialInterface.h"
|
|
#include "Materials/Material.h"
|
|
#include "VREditorUISystem.h"
|
|
#include "VIBaseTransformGizmo.h"
|
|
#include "ViewportWorldInteraction.h"
|
|
#include "VREditorPlacement.h"
|
|
#include "VREditorAvatarActor.h"
|
|
#include "VREditorTeleporter.h"
|
|
#include "VREditorAutoScaler.h"
|
|
#include "VREditorStyle.h"
|
|
#include "VREditorAssetContainer.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "CameraController.h"
|
|
#include "EngineGlobals.h"
|
|
#include "ILevelEditor.h"
|
|
#include "LevelEditor.h"
|
|
#include "LevelEditorActions.h"
|
|
#include "SLevelViewport.h"
|
|
#include "MotionControllerComponent.h"
|
|
#include "EngineAnalytics.h"
|
|
#include "IHeadMountedDisplay.h"
|
|
#include "IXRTrackingSystem.h"
|
|
#include "Interfaces/IAnalyticsProvider.h"
|
|
#include "Interfaces/IProjectManager.h"
|
|
|
|
#include "IViewportInteractionModule.h"
|
|
#include "VREditorInteractor.h"
|
|
|
|
#include "EditorWorldExtension.h"
|
|
#include "SequencerSettings.h"
|
|
#include "Kismet/GameplayStatics.h"
|
|
#include "Components/AudioComponent.h"
|
|
#include "Components/StaticMeshComponent.h"
|
|
#include "VREditorActions.h"
|
|
#include "EditorModes.h"
|
|
#include "VRModeSettings.h"
|
|
#include "IVREditorModule.h"
|
|
#include "Engine/StaticMeshActor.h"
|
|
#include "EngineUtils.h"
|
|
#include "Kismet/GameplayStatics.h"
|
|
#include "XRMotionControllerBase.h" // for FXRMotionControllerBase::Left/RightHandSourceId
|
|
#include "VREditorFloatingUI.h"
|
|
#include "AssetEditorViewportLayout.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "VREditorMode"
|
|
|
|
namespace VREd
|
|
{
|
|
static FAutoConsoleVariable DefaultVRNearClipPlane(TEXT("VREd.DefaultVRNearClipPlane"), 5.0f, TEXT("The near clip plane to use for VR"));
|
|
static FAutoConsoleVariable SlateDragDistanceOverride( TEXT( "VREd.SlateDragDistanceOverride" ), 40.0f, TEXT( "How many pixels you need to drag before a drag and drop operation starts in VR" ) );
|
|
static FAutoConsoleVariable DefaultWorldToMeters(TEXT("VREd.DefaultWorldToMeters"), 100.0f, TEXT("Default world to meters scale"));
|
|
|
|
static FAutoConsoleVariable ShowHeadVelocity( TEXT( "VREd.ShowHeadVelocity" ), 0, TEXT( "Whether to draw a debug indicator that shows how much the head is accelerating" ) );
|
|
static FAutoConsoleVariable HeadVelocitySmoothing( TEXT( "VREd.HeadVelocitySmoothing" ), 0.95f, TEXT( "How much to smooth out head velocity data" ) );
|
|
static FAutoConsoleVariable HeadVelocityMinRadius( TEXT( "VREd.HeadVelocityMinRadius" ), 0.0f, TEXT( "How big the inner circle of the head velocity ring should be" ) );
|
|
static FAutoConsoleVariable HeadVelocityMaxRadius( TEXT( "VREd.HeadVelocityMaxRadius" ), 10.0f, TEXT( "How big the outer circle of the head velocity ring should be" ) );
|
|
static FAutoConsoleVariable HeadVelocityMinLineThickness( TEXT( "VREd.HeadVelocityMinLineThickness" ), 0.05f, TEXT( "How thick the head velocity ring lines should be" ) );
|
|
static FAutoConsoleVariable HeadVelocityMaxLineThickness( TEXT( "VREd.HeadVelocityMaxLineThickness" ), 0.4f, TEXT( "How thick the head velocity ring lines should be" ) );
|
|
static FAutoConsoleVariable HeadLocationMaxVelocity( TEXT( "VREd.HeadLocationMaxVelocity" ), 25.0f, TEXT( "For head velocity indicator, the maximum location velocity in cm/s" ) );
|
|
static FAutoConsoleVariable HeadRotationMaxVelocity( TEXT( "VREd.HeadRotationMaxVelocity" ), 80.0f, TEXT( "For head velocity indicator, the maximum rotation velocity in degrees/s" ) );
|
|
static FAutoConsoleVariable HeadLocationVelocityOffset( TEXT( "VREd.HeadLocationVelocityOffset" ), TEXT( "X=20, Y=0, Z=5" ), TEXT( "Offset relative to head for location velocity debug indicator" ) );
|
|
static FAutoConsoleVariable HeadRotationVelocityOffset( TEXT( "VREd.HeadRotationVelocityOffset" ), TEXT( "X=20, Y=0, Z=-5" ), TEXT( "Offset relative to head for rotation velocity debug indicator" ) );
|
|
static FAutoConsoleVariable SFXMultiplier(TEXT("VREd.SFXMultiplier"), 1.5f, TEXT("Default Sound Effect Volume Multiplier"));
|
|
|
|
static FAutoConsoleCommand ToggleDebugMode(TEXT("VREd.ToggleDebugMode"), TEXT("Toggles debug mode of the VR Mode"), FConsoleCommandDelegate::CreateStatic(&UVREditorMode::ToggleDebugMode));
|
|
}
|
|
|
|
const TCHAR* UVREditorMode::AssetContainerPath = TEXT("/Engine/VREditor/VREditorAssetContainerData");
|
|
bool UVREditorMode::bDebugModeEnabled = false;
|
|
|
|
UVREditorMode::UVREditorMode() :
|
|
Super(),
|
|
bWantsToExitMode( false ),
|
|
bIsFullyInitialized( false ),
|
|
AppTimeModeEntered( FTimespan::Zero() ),
|
|
AvatarActor( nullptr ),
|
|
FlashlightComponent( nullptr ),
|
|
bIsFlashlightOn( false ),
|
|
MotionControllerID( 0 ), // @todo vreditor minor: We only support a single controller, and we assume the first controller are the motion controls
|
|
UISystem( nullptr ),
|
|
TeleportActor( nullptr ),
|
|
AutoScalerSystem( nullptr ),
|
|
WorldInteraction( nullptr ),
|
|
bFirstTick( true ),
|
|
AssetContainer( nullptr )
|
|
{
|
|
SetActive(false);
|
|
}
|
|
|
|
void UVREditorMode::SetHMDDeviceTypeOverride( FName InOverrideType )
|
|
{
|
|
ensureMsgf(!IsActive(), TEXT("HMD device type override should be specified before VR editor mode is entered."));
|
|
HMDDeviceTypeOverride = InOverrideType;
|
|
}
|
|
|
|
void UVREditorMode::Init()
|
|
{
|
|
// @todo vreditor urgent: Turn on global editor hacks for VR Editor mode
|
|
GEnableVREditorHacks = true;
|
|
|
|
bIsFullyInitialized = false;
|
|
bWantsToExitMode = false;
|
|
|
|
AppTimeModeEntered = FTimespan::FromSeconds( FApp::GetCurrentTime() );
|
|
|
|
// Take note of VREditor activation
|
|
if( FEngineAnalytics::IsAvailable() )
|
|
{
|
|
FEngineAnalytics::GetProvider().RecordEvent( TEXT( "Editor.Usage.InitVREditorMode" ), FAnalyticsEventAttribute( TEXT("Enterprise"), IProjectManager::Get().IsEnterpriseProject() ) );
|
|
}
|
|
|
|
// Setting up colors
|
|
Colors.SetNumZeroed( (int32)EColors::TotalCount );
|
|
{
|
|
Colors[ (int32)EColors::DefaultColor ] = FLinearColor(0.701f, 0.084f, 0.075f, 1.0f);
|
|
Colors[ (int32)EColors::SelectionColor ] = FLinearColor(1.0f, 0.467f, 0.0f, 1.f);
|
|
Colors[ (int32)EColors::WorldDraggingColor ] = FLinearColor(0.106, 0.487, 0.106, 1.0f);
|
|
Colors[ (int32)EColors::UIColor ] = FLinearColor(0.22f, 0.7f, 0.98f, 1.0f);
|
|
Colors[ (int32)EColors::UISelectionBarColor ] = FLinearColor( 0.025f, 0.025f, 0.025f, 1.0f );
|
|
Colors[ (int32)EColors::UISelectionBarHoverColor ] = FLinearColor( 0.1f, 0.1f, 0.1f, 1.0f );
|
|
Colors[ (int32)EColors::UICloseButtonColor ] = FLinearColor( 0.1f, 0.1f, 0.1f, 1.0f );
|
|
Colors[ (int32)EColors::UICloseButtonHoverColor ] = FLinearColor( 1.0f, 1.0f, 1.0f, 1.0f );
|
|
}
|
|
|
|
{
|
|
UEditorWorldExtensionCollection* Collection = GEditor->GetEditorWorldExtensionsManager()->GetEditorWorldExtensions(GetWorld());
|
|
check(Collection != nullptr);
|
|
WorldInteraction = Cast<UViewportWorldInteraction>( Collection->FindExtension( UViewportWorldInteraction::StaticClass() ) );
|
|
check( WorldInteraction != nullptr );
|
|
}
|
|
|
|
// Setup the asset container.
|
|
AssetContainer = &LoadAssetContainer();
|
|
check(AssetContainer != nullptr);
|
|
|
|
// Setup slate style
|
|
FVREditorStyle::Get();
|
|
|
|
bIsFullyInitialized = true;
|
|
}
|
|
|
|
/*
|
|
* @EventName Editor.Usage.EnterVRMode
|
|
*
|
|
* @Trigger Entering VR editing mode
|
|
*
|
|
* @Type Client
|
|
*
|
|
* @EventParam HMDDevice (string) The name of the HMD Device type
|
|
*
|
|
* @Source Editor
|
|
*
|
|
* @Owner Lauren.Ridge
|
|
*
|
|
*/
|
|
void UVREditorMode::Shutdown()
|
|
{
|
|
bIsFullyInitialized = false;
|
|
|
|
AvatarActor = nullptr;
|
|
FlashlightComponent = nullptr;
|
|
UISystem = nullptr;
|
|
TeleportActor = nullptr;
|
|
AutoScalerSystem = nullptr;
|
|
WorldInteraction = nullptr;
|
|
AssetContainer = nullptr;
|
|
|
|
// @todo vreditor urgent: Disable global editor hacks for VR Editor mode
|
|
GEnableVREditorHacks = false;
|
|
|
|
}
|
|
|
|
void UVREditorMode::AllocateInteractors()
|
|
{
|
|
class UVREditorInteractor* LeftHandInteractor = nullptr;
|
|
class UVREditorInteractor* RightHandInteractor = nullptr;
|
|
|
|
const TSoftClassPtr<UVREditorInteractor> InteractorClassSoft = GetDefault<UVRModeSettings>()->InteractorClass;
|
|
InteractorClassSoft.LoadSynchronous();
|
|
|
|
if (InteractorClassSoft.IsValid())
|
|
{
|
|
LeftHandInteractor = NewObject<UVREditorInteractor>(GetTransientPackage(), InteractorClassSoft.Get());
|
|
RightHandInteractor = NewObject<UVREditorInteractor>(GetTransientPackage(), InteractorClassSoft.Get());
|
|
}
|
|
|
|
if (LeftHandInteractor == nullptr)
|
|
{
|
|
LeftHandInteractor = NewObject<UVREditorInteractor>();
|
|
}
|
|
|
|
if (RightHandInteractor == nullptr)
|
|
{
|
|
RightHandInteractor = NewObject<UVREditorInteractor>();
|
|
}
|
|
|
|
check( LeftHandInteractor );
|
|
check( RightHandInteractor );
|
|
|
|
Interactors.Add( LeftHandInteractor );
|
|
Interactors.Add( RightHandInteractor );
|
|
|
|
LeftHandInteractor->SetControllerHandSide( FXRMotionControllerBase::LeftHandSourceId );
|
|
RightHandInteractor->SetControllerHandSide( FXRMotionControllerBase::RightHandSourceId );
|
|
|
|
for (UVREditorInteractor* Interactor : Interactors)
|
|
{
|
|
Interactor->Init( this );
|
|
WorldInteraction->AddInteractor( Interactor );
|
|
}
|
|
|
|
WorldInteraction->PairInteractors( LeftHandInteractor, RightHandInteractor );
|
|
}
|
|
|
|
void UVREditorMode::Enter()
|
|
{
|
|
BeginEntry();
|
|
SetupSubsystems();
|
|
FinishEntry();
|
|
}
|
|
|
|
void UVREditorMode::BeginEntry()
|
|
{
|
|
bWantsToExitMode = false;
|
|
|
|
{
|
|
WorldInteraction->OnPreWorldInteractionTick().AddUObject(this, &UVREditorMode::PreTick);
|
|
WorldInteraction->OnPostWorldInteractionTick().AddUObject(this, &UVREditorMode::PostTick);
|
|
}
|
|
|
|
|
|
// @todo vreditor: We need to make sure the user can never switch to orthographic mode, or activate settings that
|
|
// would disrupt the user's ability to view the VR scene.
|
|
|
|
// @todo vreditor: Don't bother drawing toolbars in VR, or other things that won't matter in VR
|
|
|
|
{
|
|
const TSharedRef< ILevelEditor >& LevelEditor = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor").GetFirstLevelEditor().ToSharedRef();
|
|
|
|
// Do we have an active perspective viewport that is valid for VR? If so, go ahead and use that.
|
|
TSharedPtr<SLevelViewport> ExistingActiveLevelViewport;
|
|
{
|
|
TSharedPtr<IAssetViewport> ActiveLevelViewport = LevelEditor->GetActiveViewportInterface();
|
|
if (ActiveLevelViewport.IsValid())
|
|
{
|
|
ExistingActiveLevelViewport = StaticCastSharedRef< SLevelViewport >(ActiveLevelViewport->AsWidget());
|
|
ExistingActiveLevelViewport->RemoveAllPreviews(true);
|
|
}
|
|
}
|
|
|
|
StartViewport(ExistingActiveLevelViewport);
|
|
|
|
if (bActuallyUsingVR)
|
|
{
|
|
// Tell Slate to require a larger pixel distance threshold before the drag starts. This is important for things
|
|
// like Content Browser drag and drop.
|
|
SavedEditorState.DragTriggerDistance = FSlateApplication::Get().GetDragTriggerDistance();
|
|
FSlateApplication::Get().SetDragTriggerDistance(VREd::SlateDragDistanceOverride->GetFloat());
|
|
|
|
// When actually in VR, make sure the transform gizmo is big!
|
|
SavedEditorState.TransformGizmoScale = WorldInteraction->GetTransformGizmoScale();
|
|
WorldInteraction->SetTransformGizmoScale(GetDefault<UVRModeSettings>()->GizmoScale);
|
|
WorldInteraction->SetShouldSuppressExistingCursor(true);
|
|
WorldInteraction->SetInVR(true);
|
|
|
|
// Take note of VREditor entering (only if actually in VR)
|
|
if (FEngineAnalytics::IsAvailable())
|
|
{
|
|
TArray< FAnalyticsEventAttribute > Attributes;
|
|
FString HMDName = GEditor->XRSystem->GetSystemName().ToString();
|
|
Attributes.Add(FAnalyticsEventAttribute(TEXT("HMDDevice"), HMDName));
|
|
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.EnterVRMode"), Attributes);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Switch us back to default mode and close any open sequencer windows
|
|
FVREditorActionCallbacks::ChangeEditorModes(FBuiltinEditorModes::EM_Default);
|
|
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
|
|
if (LevelEditorModule.GetLevelEditorTabManager())
|
|
{
|
|
if (TSharedPtr<SDockTab> SequencerTab = LevelEditorModule.GetLevelEditorTabManager()->TryInvokeTab(FTabId("Sequencer")))
|
|
{
|
|
SequencerTab->RequestCloseTab();
|
|
}
|
|
}
|
|
|
|
// Setup our avatar
|
|
if (AvatarActor == nullptr)
|
|
{
|
|
const bool bWithSceneComponent = true;
|
|
AvatarActor = SpawnTransientSceneActor<AVREditorAvatarActor>(TEXT("AvatarActor"), bWithSceneComponent);
|
|
AvatarActor->Init(this);
|
|
|
|
WorldInteraction->AddActorToExcludeFromHitTests(AvatarActor);
|
|
}
|
|
|
|
// If we're actually using VR, go ahead and disable notifications. We won't be able to see them in VR
|
|
// currently, and they can introduce performance issues if they pop up on the desktop
|
|
if (bActuallyUsingVR)
|
|
{
|
|
FSlateNotificationManager::Get().SetAllowNotifications(false);
|
|
}
|
|
|
|
/** This will make sure this is not ticking after the editor has been closed. */
|
|
GEditor->OnEditorClose().AddUObject(this, &UVREditorMode::OnEditorClosed);
|
|
}
|
|
|
|
void UVREditorMode::SetupSubsystems()
|
|
{
|
|
// Setup world interaction
|
|
// We need input preprocessing for VR so that we can receive motion controller input without any viewports having
|
|
// to be focused. This is mainly because Slate UI injected into the 3D world can cause focus to be lost unexpectedly,
|
|
// but we need the user to still be able to interact with UI.
|
|
WorldInteraction->SetUseInputPreprocessor( true );
|
|
|
|
// Motion controllers
|
|
AllocateInteractors();
|
|
|
|
if( bActuallyUsingVR )
|
|
{
|
|
// When actually using VR devices, we don't want a mouse cursor interactor
|
|
WorldInteraction->ReleaseMouseCursorInteractor();
|
|
}
|
|
|
|
// Setup the UI system
|
|
UISystem = NewObject<UVREditorUISystem>();
|
|
UISystem->Init(this);
|
|
|
|
PlacementSystem = NewObject<UVREditorPlacement>();
|
|
PlacementSystem->Init(this);
|
|
|
|
// Setup teleporter
|
|
const TSoftClassPtr<AVREditorTeleporter> TeleporterClassSoft = GetDefault<UVRModeSettings>()->TeleporterClass;
|
|
TeleporterClassSoft.LoadSynchronous();
|
|
|
|
if (TeleporterClassSoft.IsValid())
|
|
{
|
|
TeleportActor = CastChecked<AVREditorTeleporter>( SpawnTransientSceneActor(TeleporterClassSoft.Get(), TEXT( "Teleporter" ), true ) );
|
|
}
|
|
|
|
if( !TeleportActor )
|
|
{
|
|
TeleportActor = SpawnTransientSceneActor<AVREditorTeleporter>( TEXT( "Teleporter" ), true );
|
|
}
|
|
|
|
check( TeleportActor );
|
|
TeleportActor->Init( this );
|
|
WorldInteraction->AddActorToExcludeFromHitTests( TeleportActor );
|
|
|
|
// Setup autoscaler
|
|
AutoScalerSystem = NewObject<UVREditorAutoScaler>();
|
|
AutoScalerSystem->Init( this );
|
|
|
|
for (UVREditorInteractor* Interactor : Interactors)
|
|
{
|
|
Interactor->SetupComponent( AvatarActor );
|
|
}
|
|
}
|
|
|
|
void UVREditorMode::FinishEntry()
|
|
{
|
|
bFirstTick = true;
|
|
SetActive(true);
|
|
OnVRModeEntryCompleteEvent.Broadcast();
|
|
}
|
|
|
|
void UVREditorMode::Exit(const bool bShouldDisableStereo)
|
|
{
|
|
{
|
|
GetLevelViewportPossessedForVR().RemoveAllPreviews(false);
|
|
GEditor->SelectNone(true, true, false);
|
|
GEditor->NoteSelectionChange();
|
|
FVREditorActionCallbacks::ChangeEditorModes(FBuiltinEditorModes::EM_Default);
|
|
|
|
//Destroy the avatar
|
|
{
|
|
DestroyTransientActor(AvatarActor);
|
|
AvatarActor = nullptr;
|
|
FlashlightComponent = nullptr;
|
|
}
|
|
|
|
{
|
|
if(bActuallyUsingVR)
|
|
{
|
|
// Restore Slate drag trigger distance
|
|
FSlateApplication::Get().SetDragTriggerDistance( SavedEditorState.DragTriggerDistance );
|
|
|
|
// Restore gizmo size
|
|
WorldInteraction->SetTransformGizmoScale( SavedEditorState.TransformGizmoScale );
|
|
WorldInteraction->SetShouldSuppressExistingCursor(false);
|
|
|
|
// Take note of VREditor exiting (only if actually in VR)
|
|
if (FEngineAnalytics::IsAvailable())
|
|
{
|
|
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.ExitVRMode"));
|
|
}
|
|
}
|
|
|
|
CloseViewport( bShouldDisableStereo );
|
|
|
|
VREditorLevelViewportWeakPtr.Reset();
|
|
OnVREditingModeExit_Handler.ExecuteIfBound();
|
|
}
|
|
|
|
// Kill the VR editor window
|
|
TSharedPtr<SWindow> VREditorWindow( VREditorWindowWeakPtr.Pin() );
|
|
if(VREditorWindow.IsValid())
|
|
{
|
|
VREditorWindow->RequestDestroyWindow();
|
|
VREditorWindow.Reset();
|
|
}
|
|
}
|
|
|
|
// Kill subsystems
|
|
if( UISystem != nullptr )
|
|
{
|
|
UISystem->Shutdown();
|
|
UISystem->MarkAsGarbage();
|
|
UISystem = nullptr;
|
|
}
|
|
|
|
if( PlacementSystem != nullptr )
|
|
{
|
|
PlacementSystem->Shutdown();
|
|
PlacementSystem->MarkAsGarbage();
|
|
PlacementSystem = nullptr;
|
|
}
|
|
|
|
if( TeleportActor != nullptr )
|
|
{
|
|
DestroyTransientActor( TeleportActor );
|
|
TeleportActor = nullptr;
|
|
}
|
|
|
|
if( AutoScalerSystem != nullptr )
|
|
{
|
|
AutoScalerSystem->Shutdown();
|
|
AutoScalerSystem->MarkAsGarbage();
|
|
AutoScalerSystem = nullptr;
|
|
}
|
|
|
|
if( WorldInteraction != nullptr )
|
|
{
|
|
WorldInteraction->SetUseInputPreprocessor( false );
|
|
|
|
WorldInteraction->OnHandleKeyInput().RemoveAll( this );
|
|
WorldInteraction->OnPreWorldInteractionTick().RemoveAll( this );
|
|
WorldInteraction->OnPostWorldInteractionTick().RemoveAll( this );
|
|
|
|
for (UVREditorInteractor* Interactor : Interactors)
|
|
{
|
|
WorldInteraction->RemoveInteractor( Interactor );
|
|
Interactor->MarkAsGarbage();
|
|
}
|
|
Interactors.Empty();
|
|
|
|
// Restore the mouse cursor if we removed it earlier
|
|
if( bActuallyUsingVR )
|
|
{
|
|
WorldInteraction->AddMouseCursorInteractor();
|
|
WorldInteraction->SetInVR(false);
|
|
}
|
|
}
|
|
|
|
if( bActuallyUsingVR )
|
|
{
|
|
FSlateNotificationManager::Get().SetAllowNotifications( true);
|
|
}
|
|
|
|
AssetContainer = nullptr;
|
|
|
|
|
|
GEditor->OnEditorClose().RemoveAll( this );
|
|
|
|
const bool bIsInPIEOrSimulate = (GEditor->PlayWorld != nullptr) || (GEditor->bIsSimulatingInEditor);
|
|
if (bIsInPIEOrSimulate)
|
|
{
|
|
GEditor->RequestEndPlayMap();
|
|
}
|
|
|
|
bWantsToExitMode = false;
|
|
SetActive(false);
|
|
bFirstTick = false;
|
|
}
|
|
|
|
void UVREditorMode::OnEditorClosed()
|
|
{
|
|
if(IsActive())
|
|
{
|
|
Exit( false );
|
|
Shutdown();
|
|
}
|
|
}
|
|
|
|
void UVREditorMode::StartExitingVRMode()
|
|
{
|
|
bWantsToExitMode = true;
|
|
}
|
|
|
|
void UVREditorMode::OnVREditorWindowClosed( const TSharedRef<SWindow>& ClosedWindow )
|
|
{
|
|
StartExitingVRMode();
|
|
}
|
|
|
|
void UVREditorMode::PreTick( const float DeltaTime )
|
|
{
|
|
if( !bIsFullyInitialized || !IsActive() || bWantsToExitMode )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Setting the initial position and rotation based on the editor viewport when going into VR mode
|
|
if( bFirstTick && bActuallyUsingVR )
|
|
{
|
|
const FTransform RoomToWorld = GetRoomTransform();
|
|
const FTransform WorldToRoom = RoomToWorld.Inverse();
|
|
FTransform ViewportToWorld = FTransform( SavedEditorState.ViewRotation, SavedEditorState.ViewLocation );
|
|
FTransform ViewportToRoom = ( ViewportToWorld * WorldToRoom );
|
|
|
|
FTransform ViewportToRoomYaw = ViewportToRoom;
|
|
ViewportToRoomYaw.SetRotation( FQuat( FRotator( 0.0f, ViewportToRoomYaw.GetRotation().Rotator().Yaw, 0.0f ) ) );
|
|
|
|
FTransform HeadToRoomYaw = GetRoomSpaceHeadTransform();
|
|
HeadToRoomYaw.SetRotation( FQuat( FRotator( 0.0f, HeadToRoomYaw.GetRotation().Rotator().Yaw, 0.0f ) ) );
|
|
|
|
FTransform RoomToWorldYaw = RoomToWorld;
|
|
RoomToWorldYaw.SetRotation( FQuat( FRotator( 0.0f, RoomToWorldYaw.GetRotation().Rotator().Yaw, 0.0f ) ) );
|
|
|
|
FTransform ResultToWorld = ( HeadToRoomYaw.Inverse() * ViewportToRoomYaw ) * RoomToWorldYaw;
|
|
SetRoomTransform( ResultToWorld );
|
|
}
|
|
}
|
|
|
|
void UVREditorMode::PostTick( float DeltaTime )
|
|
{
|
|
if( !bIsFullyInitialized || !IsActive() || bWantsToExitMode || !VREditorLevelViewportWeakPtr.IsValid() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
TickHandle.Broadcast( DeltaTime );
|
|
UISystem->Tick( GetLevelViewportPossessedForVR().GetViewportClient().Get(), DeltaTime );
|
|
|
|
// Update avatar meshes
|
|
{
|
|
// Move our avatar mesh along with the room. We need our hand components to remain the same coordinate space as the
|
|
AvatarActor->SetActorTransform( GetRoomTransform() );
|
|
AvatarActor->TickManually( DeltaTime );
|
|
|
|
|
|
}
|
|
|
|
// Updating the scale and intensity of the flashlight according to the world scale
|
|
if (FlashlightComponent)
|
|
{
|
|
float CurrentFalloffExponent = FlashlightComponent->LightFalloffExponent;
|
|
//@todo vreditor tweak
|
|
float UpdatedFalloffExponent = FMath::Clamp(CurrentFalloffExponent / GetWorldScaleFactor(), 2.0f, 16.0f);
|
|
FlashlightComponent->SetLightFalloffExponent(UpdatedFalloffExponent);
|
|
}
|
|
|
|
if( WorldInteraction->HaveHeadTransform() && VREd::ShowHeadVelocity->GetInt() != 0 )
|
|
{
|
|
const FTransform RoomSpaceHeadToWorld = WorldInteraction->GetRoomSpaceHeadTransform();
|
|
static FTransform LastRoomSpaceHeadToWorld = RoomSpaceHeadToWorld;
|
|
|
|
const float WorldScaleFactor = WorldInteraction->GetWorldScaleFactor();
|
|
static float LastWorldScaleFactor = WorldScaleFactor;
|
|
|
|
const float MinInnerRadius = VREd::HeadVelocityMinRadius->GetFloat() * WorldScaleFactor;
|
|
const float MaxOuterRadius = VREd::HeadVelocityMaxRadius->GetFloat() * WorldScaleFactor;
|
|
const float MinThickness = VREd::HeadVelocityMinLineThickness->GetFloat() * WorldScaleFactor;
|
|
const float MaxThickness = VREd::HeadVelocityMaxLineThickness->GetFloat() * WorldScaleFactor;
|
|
|
|
const float MaxLocationVelocity = VREd::HeadLocationMaxVelocity->GetFloat(); // cm/s
|
|
const float MaxRotationVelocity = VREd::HeadRotationMaxVelocity->GetFloat(); // degrees/s
|
|
|
|
const float LocationVelocity = ( LastRoomSpaceHeadToWorld.GetLocation() / LastWorldScaleFactor - RoomSpaceHeadToWorld.GetLocation() / WorldScaleFactor ).Size() / DeltaTime;
|
|
|
|
const float YawVelocity = FMath::Abs( FMath::FindDeltaAngleDegrees( LastRoomSpaceHeadToWorld.GetRotation().Rotator().Yaw, RoomSpaceHeadToWorld.GetRotation().Rotator().Yaw ) ) / DeltaTime;
|
|
const float PitchVelocity = FMath::Abs( FMath::FindDeltaAngleDegrees( LastRoomSpaceHeadToWorld.GetRotation().Rotator().Pitch, RoomSpaceHeadToWorld.GetRotation().Rotator().Pitch ) ) / DeltaTime;
|
|
const float RollVelocity = FMath::Abs( FMath::FindDeltaAngleDegrees( LastRoomSpaceHeadToWorld.GetRotation().Rotator().Roll, RoomSpaceHeadToWorld.GetRotation().Rotator().Roll ) ) / DeltaTime;
|
|
const float RotationVelocity = YawVelocity + PitchVelocity + RollVelocity;
|
|
|
|
static float LastLocationVelocity = LocationVelocity;
|
|
static float LastRotationVelocity = RotationVelocity;
|
|
|
|
const float SmoothLocationVelocity = FMath::Lerp( LocationVelocity, LastLocationVelocity, VREd::HeadVelocitySmoothing->GetFloat() );
|
|
const float SmoothRotationVelocity = FMath::Lerp( RotationVelocity, LastRotationVelocity, VREd::HeadVelocitySmoothing->GetFloat() );
|
|
|
|
LastLocationVelocity = SmoothLocationVelocity;
|
|
LastRotationVelocity = SmoothRotationVelocity;
|
|
|
|
LastRoomSpaceHeadToWorld = RoomSpaceHeadToWorld;
|
|
LastWorldScaleFactor = WorldScaleFactor;
|
|
|
|
const float LocationVelocityAlpha = FMath::Clamp( SmoothLocationVelocity / MaxLocationVelocity, 0.0f, 1.0f );
|
|
const float RotationVelocityAlpha = FMath::Clamp( SmoothRotationVelocity / MaxRotationVelocity, 0.0f, 1.0f );
|
|
|
|
const FTransform HeadToWorld = WorldInteraction->GetHeadTransform();
|
|
|
|
{
|
|
FVector HeadLocationVelocityOffset = FVector::ZeroVector;
|
|
HeadLocationVelocityOffset.InitFromString( VREd::HeadLocationVelocityOffset->GetString() );
|
|
HeadLocationVelocityOffset *= WorldScaleFactor;
|
|
|
|
const FColor Color = FColor::MakeFromColorTemperature( 6000.0f - LocationVelocityAlpha * 5000.0f );
|
|
const float Thickness = FMath::Lerp( MinThickness, MaxThickness, LocationVelocityAlpha );
|
|
const FTransform UIToHeadTransform = FTransform( FRotator( 0.0f, 0.0f, 0.0f ).Quaternion(), HeadLocationVelocityOffset );
|
|
const FTransform UIToWorld = UIToHeadTransform * HeadToWorld;
|
|
DrawDebug2DDonut( GetWorld(), UIToWorld.ToMatrixNoScale(), MinInnerRadius, FMath::Lerp( MinInnerRadius, MaxOuterRadius, LocationVelocityAlpha ), 64, Color, false, 0.0f, SDPG_World, Thickness );
|
|
}
|
|
|
|
{
|
|
FVector HeadRotationVelocityOffset = FVector::ZeroVector;
|
|
HeadRotationVelocityOffset.InitFromString( VREd::HeadRotationVelocityOffset->GetString() );
|
|
HeadRotationVelocityOffset *= WorldScaleFactor;
|
|
|
|
const FColor Color = FColor::MakeFromColorTemperature( 6000.0f - RotationVelocityAlpha * 5000.0f );
|
|
const float Thickness = FMath::Lerp( MinThickness, MaxThickness, RotationVelocityAlpha );
|
|
const FTransform UIToHeadTransform = FTransform( FRotator( 0.0f, 0.0f, 0.0f ).Quaternion(), HeadRotationVelocityOffset );
|
|
const FTransform UIToWorld = UIToHeadTransform * HeadToWorld;
|
|
DrawDebug2DDonut( GetWorld(), UIToWorld.ToMatrixNoScale(), MinInnerRadius, FMath::Lerp( MinInnerRadius, MaxOuterRadius, RotationVelocityAlpha ), 64, Color, false, 0.0f, SDPG_World, Thickness );
|
|
}
|
|
}
|
|
|
|
bFirstTick = false;
|
|
}
|
|
|
|
FTransform UVREditorMode::GetRoomTransform() const
|
|
{
|
|
return WorldInteraction->GetRoomTransform();
|
|
}
|
|
|
|
void UVREditorMode::SetRoomTransform( const FTransform& NewRoomTransform )
|
|
{
|
|
WorldInteraction->SetRoomTransform( NewRoomTransform );
|
|
}
|
|
|
|
FTransform UVREditorMode::GetRoomSpaceHeadTransform() const
|
|
{
|
|
return WorldInteraction->GetRoomSpaceHeadTransform();
|
|
}
|
|
|
|
FTransform UVREditorMode::GetHeadTransform() const
|
|
{
|
|
return WorldInteraction->GetHeadTransform();
|
|
}
|
|
|
|
const UViewportWorldInteraction& UVREditorMode::GetWorldInteraction() const
|
|
{
|
|
return *WorldInteraction;
|
|
}
|
|
|
|
UViewportWorldInteraction& UVREditorMode::GetWorldInteraction()
|
|
{
|
|
return *WorldInteraction;
|
|
}
|
|
|
|
bool UVREditorMode::IsFullyInitialized() const
|
|
{
|
|
return bIsFullyInitialized;
|
|
}
|
|
|
|
bool UVREditorMode::IsShowingRadialMenu(const UVREditorInteractor* Interactor) const
|
|
{
|
|
return UISystem->IsShowingRadialMenu(Interactor);
|
|
}
|
|
|
|
const SLevelViewport& UVREditorMode::GetLevelViewportPossessedForVR() const
|
|
{
|
|
return *VREditorLevelViewportWeakPtr.Pin();
|
|
}
|
|
|
|
SLevelViewport& UVREditorMode::GetLevelViewportPossessedForVR()
|
|
{
|
|
return *VREditorLevelViewportWeakPtr.Pin();
|
|
}
|
|
|
|
|
|
float UVREditorMode::GetWorldScaleFactor() const
|
|
{
|
|
return WorldInteraction->GetWorldScaleFactor();
|
|
}
|
|
|
|
void UVREditorMode::ToggleFlashlight( UVREditorInteractor* Interactor )
|
|
{
|
|
UVREditorInteractor* MotionControllerInteractor = Cast<UVREditorInteractor>( Interactor );
|
|
if ( MotionControllerInteractor )
|
|
{
|
|
if ( FlashlightComponent == nullptr )
|
|
{
|
|
FlashlightComponent = NewObject<USpotLightComponent>( AvatarActor );
|
|
AvatarActor->AddOwnedComponent( FlashlightComponent );
|
|
FlashlightComponent->RegisterComponent();
|
|
FlashlightComponent->SetMobility( EComponentMobility::Movable );
|
|
FlashlightComponent->SetCastShadows( false );
|
|
FlashlightComponent->bUseInverseSquaredFalloff = false;
|
|
//@todo vreditor tweak
|
|
FlashlightComponent->SetLightFalloffExponent( 8.0f );
|
|
FlashlightComponent->SetIntensity( 20.0f );
|
|
FlashlightComponent->SetOuterConeAngle( 25.0f );
|
|
FlashlightComponent->SetInnerConeAngle( 25.0f );
|
|
|
|
}
|
|
|
|
const FAttachmentTransformRules AttachmentTransformRules = FAttachmentTransformRules( EAttachmentRule::KeepRelative, true );
|
|
FlashlightComponent->AttachToComponent( MotionControllerInteractor->GetMotionControllerComponent(), AttachmentTransformRules );
|
|
bIsFlashlightOn = !bIsFlashlightOn;
|
|
FlashlightComponent->SetVisibility( bIsFlashlightOn );
|
|
}
|
|
}
|
|
|
|
void UVREditorMode::CycleTransformGizmoHandleType()
|
|
{
|
|
EGizmoHandleTypes NewGizmoType = (EGizmoHandleTypes)( (uint8)WorldInteraction->GetCurrentGizmoType() + 1 );
|
|
|
|
if( NewGizmoType > EGizmoHandleTypes::Scale )
|
|
{
|
|
NewGizmoType = EGizmoHandleTypes::All;
|
|
}
|
|
|
|
WorldInteraction->SetGizmoHandleType( NewGizmoType );
|
|
}
|
|
|
|
FName UVREditorMode::GetHMDDeviceType() const
|
|
{
|
|
if (HMDDeviceTypeOverride != NAME_None)
|
|
{
|
|
return HMDDeviceTypeOverride;
|
|
}
|
|
|
|
return GEngine->XRSystem.IsValid() ? GEngine->XRSystem->GetSystemName() : FName();
|
|
}
|
|
|
|
FLinearColor UVREditorMode::GetColor( const EColors Color ) const
|
|
{
|
|
return Colors[ (int32)Color ];
|
|
}
|
|
|
|
float UVREditorMode::GetDefaultVRNearClipPlane() const
|
|
{
|
|
return VREd::DefaultVRNearClipPlane->GetFloat();
|
|
}
|
|
|
|
void UVREditorMode::RefreshVREditorSequencer(class ISequencer* InCurrentSequencer)
|
|
{
|
|
CurrentSequencer = InCurrentSequencer;
|
|
// Tell the VR Editor UI system to refresh the Sequencer UI
|
|
if (bActuallyUsingVR && UISystem != nullptr)
|
|
{
|
|
GetUISystem().UpdateSequencerUI();
|
|
}
|
|
}
|
|
|
|
void UVREditorMode::RefreshActorPreviewWidget(TSharedRef<SWidget> InWidget, int32 Index, AActor *Actor, bool bIsPanelDetached)
|
|
{
|
|
if (bActuallyUsingVR && UISystem != nullptr)
|
|
{
|
|
if (bIsPanelDetached)
|
|
{
|
|
GetUISystem().UpdateDetachedActorPreviewUI(InWidget, Index);
|
|
}
|
|
else
|
|
{
|
|
GetUISystem().UpdateActorPreviewUI(InWidget, Index, Actor);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void UVREditorMode::UpdateExternalUMGUI(const FVREditorFloatingUICreationContext& CreationContext)
|
|
{
|
|
if (bActuallyUsingVR && UISystem != nullptr)
|
|
{
|
|
GetUISystem().UpdateExternalUMGUI(CreationContext);
|
|
}
|
|
}
|
|
|
|
void UVREditorMode::UpdateExternalSlateUI(TSharedRef<SWidget> InWidget, FName Name, FVector2D InSize)
|
|
{
|
|
if (bActuallyUsingVR && UISystem != nullptr)
|
|
{
|
|
GetUISystem().UpdateExternalSlateUI(InWidget, Name, InSize);
|
|
}
|
|
}
|
|
|
|
class ISequencer* UVREditorMode::GetCurrentSequencer()
|
|
{
|
|
return CurrentSequencer;
|
|
}
|
|
|
|
bool UVREditorMode::IsHandAimingTowardsCapsule(UViewportInteractor* Interactor, const FTransform& CapsuleTransform, FVector CapsuleStart, const FVector CapsuleEnd, const float CapsuleRadius, const float MinDistanceToCapsule, const FVector CapsuleFrontDirection, const float MinDotForAimingAtCapsule) const
|
|
{
|
|
bool bIsAimingTowards = false;
|
|
const float WorldScaleFactor = GetWorldScaleFactor();
|
|
|
|
FVector LaserPointerStart, LaserPointerEnd;
|
|
if( Interactor->GetLaserPointer( /* Out */ LaserPointerStart, /* Out */ LaserPointerEnd ) )
|
|
{
|
|
const FVector LaserPointerStartInCapsuleSpace = CapsuleTransform.InverseTransformPosition( LaserPointerStart );
|
|
const FVector LaserPointerEndInCapsuleSpace = CapsuleTransform.InverseTransformPosition( LaserPointerEnd );
|
|
|
|
FVector ClosestPointOnLaserPointer, ClosestPointOnUICapsule;
|
|
FMath::SegmentDistToSegment(
|
|
LaserPointerStartInCapsuleSpace, LaserPointerEndInCapsuleSpace,
|
|
CapsuleStart, CapsuleEnd,
|
|
/* Out */ ClosestPointOnLaserPointer,
|
|
/* Out */ ClosestPointOnUICapsule );
|
|
|
|
const bool bIsClosestPointInsideCapsule = ( ClosestPointOnLaserPointer - ClosestPointOnUICapsule ).Size() <= CapsuleRadius;
|
|
|
|
const FVector TowardLaserPointerVector = ( ClosestPointOnLaserPointer - ClosestPointOnUICapsule ).GetSafeNormal();
|
|
|
|
// Apply capsule radius
|
|
ClosestPointOnUICapsule += TowardLaserPointerVector * CapsuleRadius;
|
|
|
|
if( false ) // @todo vreditor debug
|
|
{
|
|
const float RenderCapsuleLength = ( CapsuleEnd - CapsuleStart ).Size() + CapsuleRadius * 2.0f;
|
|
// @todo vreditor: This capsule draws with the wrong orientation
|
|
if( false )
|
|
{
|
|
DrawDebugCapsule( GetWorld(), CapsuleTransform.TransformPosition( CapsuleStart + ( CapsuleEnd - CapsuleStart ) * 0.5f ), RenderCapsuleLength * 0.5f, CapsuleRadius, CapsuleTransform.GetRotation() * FRotator( 90.0f, 0, 0 ).Quaternion(), FColor::Green, false, 0.0f );
|
|
}
|
|
DrawDebugLine( GetWorld(), CapsuleTransform.TransformPosition( ClosestPointOnLaserPointer ), CapsuleTransform.TransformPosition( ClosestPointOnUICapsule ), FColor::Green, false, 0.0f );
|
|
DrawDebugSphere( GetWorld(), CapsuleTransform.TransformPosition( ClosestPointOnLaserPointer ), 1.5f * WorldScaleFactor, 32, FColor::Red, false, 0.0f );
|
|
DrawDebugSphere( GetWorld(), CapsuleTransform.TransformPosition( ClosestPointOnUICapsule ), 1.5f * WorldScaleFactor, 32, FColor::Green, false, 0.0f );
|
|
}
|
|
|
|
// If we're really close to the capsule
|
|
if( bIsClosestPointInsideCapsule ||
|
|
( ClosestPointOnUICapsule - ClosestPointOnLaserPointer ).Size() <= MinDistanceToCapsule )
|
|
{
|
|
const FVector LaserPointerDirectionInCapsuleSpace = ( LaserPointerEndInCapsuleSpace - LaserPointerStartInCapsuleSpace ).GetSafeNormal();
|
|
|
|
if( false ) // @todo vreditor debug
|
|
{
|
|
DrawDebugLine( GetWorld(), CapsuleTransform.TransformPosition( FVector::ZeroVector ), CapsuleTransform.TransformPosition( CapsuleFrontDirection * 5.0f ), FColor::Yellow, false, 0.0f );
|
|
DrawDebugLine( GetWorld(), CapsuleTransform.TransformPosition( FVector::ZeroVector ), CapsuleTransform.TransformPosition( -LaserPointerDirectionInCapsuleSpace * 5.0f ), FColor::Purple, false, 0.0f );
|
|
}
|
|
|
|
const float Dot = FVector::DotProduct( CapsuleFrontDirection, -LaserPointerDirectionInCapsuleSpace );
|
|
if( Dot >= MinDotForAimingAtCapsule )
|
|
{
|
|
bIsAimingTowards = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bIsAimingTowards;
|
|
}
|
|
|
|
UVREditorInteractor* UVREditorMode::GetHandInteractor( const EControllerHand ControllerHand ) const
|
|
{
|
|
for (UVREditorInteractor* Interactor : Interactors)
|
|
{
|
|
if (Interactor->GetControllerSide() == ControllerHand)
|
|
{
|
|
return Interactor;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void UVREditorMode::SnapSelectedActorsToGround()
|
|
{
|
|
TSharedPtr<SLevelViewport> LevelEditorViewport = StaticCastSharedPtr<SLevelViewport>(WorldInteraction->GetDefaultOptionalViewportClient()->GetEditorViewportWidget());
|
|
if (LevelEditorViewport.IsValid())
|
|
{
|
|
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
|
|
const FLevelEditorCommands& Commands = LevelEditorModule.GetLevelEditorCommands();
|
|
const TSharedPtr< FUICommandList >& CommandList = LevelEditorViewport->GetParentLevelEditor().Pin()->GetLevelEditorActions(); //@todo vreditor: Cast on leveleditor
|
|
|
|
CommandList->ExecuteAction(Commands.SnapBottomCenterBoundsToFloor.ToSharedRef());
|
|
|
|
// Force transformables to refresh
|
|
GEditor->NoteSelectionChange();
|
|
}
|
|
}
|
|
|
|
const UVREditorMode::FSavedEditorState& UVREditorMode::GetSavedEditorState() const
|
|
{
|
|
return SavedEditorState;
|
|
}
|
|
|
|
void UVREditorMode::SaveSequencerSettings(bool bInKeyAllEnabled, EAutoChangeMode InAutoChangeMode, const class USequencerSettings& InSequencerSettings)
|
|
{
|
|
SavedEditorState.bKeyAllEnabled = bInKeyAllEnabled;
|
|
SavedEditorState.AutoChangeMode = InAutoChangeMode;
|
|
}
|
|
|
|
void UVREditorMode::TransitionWorld(UWorld* NewWorld, EEditorWorldExtensionTransitionState TransitionState)
|
|
{
|
|
Super::TransitionWorld(NewWorld, TransitionState);
|
|
|
|
UISystem->TransitionWorld(NewWorld, TransitionState);
|
|
}
|
|
|
|
void UVREditorMode::StartViewport(TSharedPtr<SLevelViewport> Viewport)
|
|
{
|
|
if (false)
|
|
{
|
|
const TSharedRef< ILevelEditor >& LevelEditor = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor").GetFirstLevelEditor().ToSharedRef();
|
|
|
|
// @todo vreditor: The resolution we set here doesn't matter, as HMDs will draw at their native resolution
|
|
// no matter what. We should probably allow the window to be freely resizable by the user
|
|
// @todo vreditor: Should save and restore window position and size settings
|
|
FVector2D WindowSize;
|
|
{
|
|
IHeadMountedDisplay::MonitorInfo HMDMonitorInfo;
|
|
if (bActuallyUsingVR && GEngine->XRSystem->GetHMDDevice() && GEngine->XRSystem->GetHMDDevice()->GetHMDMonitorInfo(HMDMonitorInfo))
|
|
{
|
|
WindowSize = FVector2D(HMDMonitorInfo.ResolutionX, HMDMonitorInfo.ResolutionY);
|
|
}
|
|
else
|
|
{
|
|
// @todo vreditor: Hard-coded failsafe window size
|
|
WindowSize = FVector2D(1920.0f, 1080.0f);
|
|
}
|
|
}
|
|
|
|
// @todo vreditor: Use SLevelEditor::GetTableTitle() for the VR window title (needs dynamic update)
|
|
const FText VREditorWindowTitle = NSLOCTEXT("VREditor", "VRWindowTitle", "Unreal Editor VR");
|
|
|
|
TSharedRef< SWindow > VREditorWindow = SNew(SWindow)
|
|
.Title(VREditorWindowTitle)
|
|
.ClientSize(WindowSize)
|
|
.AutoCenter(EAutoCenter::PreferredWorkArea)
|
|
.UseOSWindowBorder(true) // @todo vreditor: Allow window to be freely resized? Shouldn't really hurt anything. We should save position/size too.
|
|
.SizingRule(ESizingRule::UserSized);
|
|
this->VREditorWindowWeakPtr = VREditorWindow;
|
|
|
|
FAssetEditorViewportConstructionArgs ConstructionArgs;
|
|
ConstructionArgs.ViewportType = LVT_Perspective;
|
|
ConstructionArgs.IsEnabled = TAttribute<bool>(FSlateApplication::Get().GetNormalExecutionAttribute());
|
|
ConstructionArgs.bRealtime = true;
|
|
|
|
Viewport =
|
|
SNew(SLevelViewport, ConstructionArgs)
|
|
.ParentLevelEditor(LevelEditor);
|
|
|
|
// Allow the editor to keep track of this editor viewport. Because it's not inside of a normal tab,
|
|
// we need to explicitly tell the level editor about it
|
|
LevelEditor->AddStandaloneLevelViewport(Viewport.ToSharedRef());
|
|
|
|
VREditorWindow->SetContent(Viewport.ToSharedRef());
|
|
|
|
// NOTE: We're intentionally not adding this window natively parented to the main frame window, because we don't want it
|
|
// to minimize/restore when the main frame is minimized/restored
|
|
FSlateApplication::Get().AddWindow(VREditorWindow);
|
|
|
|
VREditorWindow->SetOnWindowClosed(FOnWindowClosed::CreateUObject(this, &UVREditorMode::OnVREditorWindowClosed));
|
|
|
|
VREditorWindow->BringToFront(); // @todo vreditor: Not sure if this is needed, especially if we decide the window should be hidden (copied this from PIE code)
|
|
}
|
|
else
|
|
{
|
|
if (bActuallyUsingVR && !Viewport->IsImmersive())
|
|
{
|
|
// Switch to immersive mode
|
|
const bool bWantImmersive = true;
|
|
const bool bAllowAnimation = false;
|
|
Viewport->MakeImmersive(bWantImmersive, bAllowAnimation);
|
|
}
|
|
}
|
|
|
|
this->VREditorLevelViewportWeakPtr = Viewport;
|
|
|
|
{
|
|
FLevelEditorViewportClient& VRViewportClient = Viewport->GetLevelViewportClient();
|
|
FEditorViewportClient& VREditorViewportClient = VRViewportClient;
|
|
|
|
// Make sure we are in perspective mode
|
|
// @todo vreditor: We should never allow ortho switching while in VR
|
|
SavedEditorState.ViewportType = VREditorViewportClient.GetViewportType();
|
|
VREditorViewportClient.SetViewportType(LVT_Perspective);
|
|
|
|
// Set the initial camera location
|
|
// @todo vreditor: This should instead be calculated using the currently active perspective camera's
|
|
// location and orientation, compensating for the current HMD offset from the tracking space origin.
|
|
// Perhaps, we also want to teleport the original viewport's camera back when we exit this mode, too!
|
|
// @todo vreditor: Should save and restore camera position and any other settings we change (viewport type, pitch locking, etc.)
|
|
SavedEditorState.ViewLocation = VRViewportClient.GetViewLocation();
|
|
SavedEditorState.ViewRotation = VRViewportClient.GetViewRotation();
|
|
|
|
// Don't allow the tracking space to pitch up or down. People hate that in VR.
|
|
// @todo vreditor: This doesn't seem to prevent people from pitching the camera with RMB drag
|
|
SavedEditorState.bLockedPitch = VRViewportClient.GetCameraController()->GetConfig().bLockedPitch;
|
|
if (bActuallyUsingVR)
|
|
{
|
|
VRViewportClient.GetCameraController()->AccessConfig().bLockedPitch = true;
|
|
}
|
|
|
|
// Set "game mode" to be enabled, to get better performance. Also hit proxies won't work in VR, anyway
|
|
VREditorViewportClient.SetVREditView(true);
|
|
|
|
SavedEditorState.bRealTime = VREditorViewportClient.IsRealtime();
|
|
VREditorViewportClient.SetRealtime(true);
|
|
|
|
SavedEditorState.ShowFlags = VREditorViewportClient.EngineShowFlags;
|
|
|
|
// Make sure the mode widgets don't come back when users click on things
|
|
VRViewportClient.bAlwaysShowModeWidgetAfterSelectionChanges = false;
|
|
|
|
// Force tiny near clip plane distance, because user can scale themselves to be very small.
|
|
SavedEditorState.NearClipPlane = GNearClippingPlane;
|
|
GNearClippingPlane = GetDefaultVRNearClipPlane();
|
|
|
|
SavedEditorState.bOnScreenMessages = GAreScreenMessagesEnabled;
|
|
GAreScreenMessagesEnabled = false;
|
|
|
|
// Save the world to meters scale
|
|
{
|
|
const float DefaultWorldToMeters = VREd::DefaultWorldToMeters->GetFloat();
|
|
const float SavedWorldToMeters = DefaultWorldToMeters != 0.0f ? DefaultWorldToMeters : VRViewportClient.GetWorld()->GetWorldSettings()->WorldToMeters;
|
|
SavedEditorState.WorldToMetersScale = SavedWorldToMeters;
|
|
}
|
|
|
|
if (bActuallyUsingVR)
|
|
{
|
|
SavedEditorState.TrackingOrigin = GEngine->XRSystem->GetTrackingOrigin();
|
|
GEngine->XRSystem->SetTrackingOrigin(EHMDTrackingOrigin::Floor);
|
|
}
|
|
|
|
// Make the new viewport the active level editing viewport right away
|
|
GCurrentLevelEditingViewportClient = &VRViewportClient;
|
|
|
|
// Change viewport settings to more VR-friendly sequencer settings
|
|
SavedEditorState.bCinematicControlViewport = VRViewportClient.AllowsCinematicControl();
|
|
VRViewportClient.SetAllowCinematicControl(false);
|
|
// Need to force fading and color scaling off in case we enter VR editing mode with a sequence open
|
|
VRViewportClient.bEnableFading = false;
|
|
VRViewportClient.bEnableColorScaling = false;
|
|
VRViewportClient.Invalidate(true);
|
|
}
|
|
|
|
if (bActuallyUsingVR && GEngine->XRSystem.IsValid())
|
|
{
|
|
Viewport->EnableStereoRendering( bActuallyUsingVR );
|
|
Viewport->SetRenderDirectlyToWindow( bActuallyUsingVR );
|
|
|
|
GEngine->StereoRenderingDevice->EnableStereo(true);
|
|
}
|
|
|
|
if (WorldInteraction != nullptr)
|
|
{
|
|
TSharedPtr<FEditorViewportClient> VRViewportClient = Viewport->GetViewportClient();
|
|
WorldInteraction->SetDefaultOptionalViewportClient(VRViewportClient);
|
|
}
|
|
}
|
|
|
|
void UVREditorMode::CloseViewport( const bool bShouldDisableStereo )
|
|
{
|
|
if (bActuallyUsingVR && GEngine->XRSystem.IsValid() && bShouldDisableStereo)
|
|
{
|
|
GEngine->StereoRenderingDevice->EnableStereo(false);
|
|
}
|
|
|
|
TSharedPtr<SLevelViewport> VREditorLevelViewport(VREditorLevelViewportWeakPtr.Pin());
|
|
if (VREditorLevelViewport.IsValid())
|
|
{
|
|
if( bShouldDisableStereo && bActuallyUsingVR )
|
|
{
|
|
VREditorLevelViewport->EnableStereoRendering(false);
|
|
VREditorLevelViewport->SetRenderDirectlyToWindow(false);
|
|
}
|
|
|
|
{
|
|
FLevelEditorViewportClient& VRViewportClient = VREditorLevelViewport->GetLevelViewportClient();
|
|
FEditorViewportClient& VREditorViewportClient = VRViewportClient;
|
|
|
|
// Restore settings that we changed on the viewport
|
|
VREditorViewportClient.SetViewportType(SavedEditorState.ViewportType);
|
|
VRViewportClient.GetCameraController()->AccessConfig().bLockedPitch = SavedEditorState.bLockedPitch;
|
|
VRViewportClient.bAlwaysShowModeWidgetAfterSelectionChanges = SavedEditorState.bAlwaysShowModeWidgetAfterSelectionChanges;
|
|
VRViewportClient.EngineShowFlags = SavedEditorState.ShowFlags;
|
|
VRViewportClient.SetVREditView(false);
|
|
VRViewportClient.SetAllowCinematicControl(SavedEditorState.bCinematicControlViewport);
|
|
VRViewportClient.bEnableFading = true;
|
|
VRViewportClient.bEnableColorScaling = true;
|
|
VRViewportClient.Invalidate(true);
|
|
|
|
if (bActuallyUsingVR)
|
|
{
|
|
VRViewportClient.SetViewLocation(GetHeadTransform().GetLocation());
|
|
|
|
FRotator HeadRotationNoRoll = GetHeadTransform().GetRotation().Rotator();
|
|
HeadRotationNoRoll.Roll = 0.0f;
|
|
VRViewportClient.SetViewRotation(HeadRotationNoRoll); // Use SavedEditorState.ViewRotation to go back to start rot
|
|
}
|
|
|
|
VRViewportClient.SetRealtime(SavedEditorState.bRealTime);
|
|
|
|
GNearClippingPlane = SavedEditorState.NearClipPlane;
|
|
GAreScreenMessagesEnabled = SavedEditorState.bOnScreenMessages;
|
|
|
|
if (bActuallyUsingVR)
|
|
{
|
|
GEngine->XRSystem->SetTrackingOrigin(SavedEditorState.TrackingOrigin);
|
|
}
|
|
|
|
RestoreWorldToMeters();
|
|
}
|
|
|
|
if (bActuallyUsingVR && bShouldDisableStereo)
|
|
{
|
|
// Leave immersive mode
|
|
const bool bWantImmersive = false;
|
|
const bool bAllowAnimation = false;
|
|
VREditorLevelViewport->MakeImmersive(bWantImmersive, bAllowAnimation);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void UVREditorMode::RestoreWorldToMeters()
|
|
{
|
|
const float DefaultWorldToMeters = VREd::DefaultWorldToMeters->GetFloat();
|
|
GetWorld()->GetWorldSettings()->WorldToMeters = DefaultWorldToMeters != 0.0f ? DefaultWorldToMeters : SavedEditorState.WorldToMetersScale;
|
|
ENGINE_API extern float GNewWorldToMetersScale;
|
|
GNewWorldToMetersScale = 0.0f;
|
|
}
|
|
|
|
UStaticMeshComponent* UVREditorMode::CreateMotionControllerMesh(AActor* OwningActor, USceneComponent* AttachmentToComponent, UStaticMesh* OptionalControllerMesh)
|
|
{
|
|
UStaticMesh* ControllerMesh = OptionalControllerMesh;
|
|
|
|
if (ControllerMesh == nullptr)
|
|
{
|
|
if (GetHMDDeviceType() == FName(TEXT("SteamVR")))
|
|
{
|
|
ControllerMesh = AssetContainer->VivePreControllerMesh;
|
|
}
|
|
else if (GetHMDDeviceType() == FName(TEXT("OculusHMD")))
|
|
{
|
|
ControllerMesh = AssetContainer->OculusControllerMesh;
|
|
}
|
|
else
|
|
{
|
|
ControllerMesh = AssetContainer->GenericControllerMesh;
|
|
}
|
|
}
|
|
|
|
return CreateMesh(OwningActor, ControllerMesh, AttachmentToComponent);
|
|
}
|
|
|
|
UStaticMeshComponent* UVREditorMode::CreateMesh( AActor* OwningActor, const FString& MeshName, USceneComponent* AttachmentToComponent /*= nullptr */ )
|
|
{
|
|
UStaticMesh* Mesh = LoadObject<UStaticMesh>(nullptr, *MeshName);
|
|
check(Mesh != nullptr);
|
|
return CreateMesh(OwningActor, Mesh, AttachmentToComponent);
|
|
}
|
|
|
|
UStaticMeshComponent* UVREditorMode::CreateMesh(AActor* OwningActor, UStaticMesh* Mesh, USceneComponent* AttachmentToComponent /*= nullptr */)
|
|
{
|
|
UStaticMeshComponent* CreatedMeshComponent = NewObject<UStaticMeshComponent>(OwningActor);
|
|
OwningActor->AddOwnedComponent(CreatedMeshComponent);
|
|
if (AttachmentToComponent != nullptr)
|
|
{
|
|
CreatedMeshComponent->SetupAttachment(AttachmentToComponent);
|
|
}
|
|
|
|
CreatedMeshComponent->RegisterComponent();
|
|
|
|
CreatedMeshComponent->SetStaticMesh(Mesh);
|
|
CreatedMeshComponent->SetMobility(EComponentMobility::Movable);
|
|
CreatedMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
|
CreatedMeshComponent->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
|
|
return CreatedMeshComponent;
|
|
}
|
|
|
|
const UVREditorAssetContainer& UVREditorMode::GetAssetContainer() const
|
|
{
|
|
return *AssetContainer;
|
|
}
|
|
|
|
UVREditorAssetContainer& UVREditorMode::LoadAssetContainer()
|
|
{
|
|
UVREditorAssetContainer* AssetContainer = LoadObject<UVREditorAssetContainer>(nullptr, UVREditorMode::AssetContainerPath);
|
|
checkf(AssetContainer, TEXT("Failed to load ViewportInteractionAssetContainer (%s). See log for reason."), UVREditorMode::AssetContainerPath);
|
|
return *AssetContainer;
|
|
}
|
|
|
|
void UVREditorMode::PlaySound(USoundBase* SoundBase, const FVector& InWorldLocation, const float InVolume /*= 1.0f*/)
|
|
{
|
|
if (IsActive() && bIsFullyInitialized && GEditor != nullptr && GEditor->CanPlayEditorSound())
|
|
{
|
|
const float Volume = InVolume*VREd::SFXMultiplier->GetFloat();
|
|
UGameplayStatics::PlaySoundAtLocation(GetWorld(), SoundBase, InWorldLocation, Volume);
|
|
}
|
|
}
|
|
|
|
bool UVREditorMode::IsAimingTeleport() const
|
|
{
|
|
return TeleportActor->IsAiming();
|
|
}
|
|
|
|
void UVREditorMode::ToggleDebugMode()
|
|
{
|
|
UVREditorMode::bDebugModeEnabled = !UVREditorMode::bDebugModeEnabled;
|
|
IVREditorModule& VREditorModule = IVREditorModule::Get();
|
|
UVREditorMode* VRMode = VREditorModule.GetVRMode();
|
|
if (VRMode != nullptr)
|
|
{
|
|
VRMode->OnToggleDebugMode().Broadcast(UVREditorMode::bDebugModeEnabled);
|
|
}
|
|
}
|
|
|
|
bool UVREditorMode::IsDebugModeEnabled()
|
|
{
|
|
return UVREditorMode::bDebugModeEnabled;
|
|
}
|
|
|
|
class AVREditorTeleporter* UVREditorMode::GetTeleportActor()
|
|
{
|
|
return TeleportActor;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|