Files
UnrealEngine/Engine/Source/Editor/VREditor/Private/VREditorModeBase.cpp
2025-05-18 13:04:45 +08:00

317 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "VREditorModeBase.h"
#include "CameraController.h"
#include "Engine/Engine.h"
#include "Framework/Application/SlateApplication.h"
#include "GameFramework/WorldSettings.h"
#include "IHeadMountedDisplay.h"
#include "ILevelEditor.h"
#include "IXRTrackingSystem.h"
#include "LevelEditor.h"
#include "LevelViewportActions.h"
#include "Modules/ModuleManager.h"
#include "SLevelViewport.h"
#include "RenderCore.h"
namespace VREd
{
extern FAutoConsoleVariable DefaultVRNearClipPlane;
extern FAutoConsoleVariable DefaultWorldToMeters;
}
void UVREditorModeBase::Init()
{
Super::Init();
// @todo vreditor urgent: Turn on global editor hacks for VR Editor mode
GEnableVREditorHacks = true;
if (!SavedEditorStatePtr)
{
SavedEditorStatePtr = CreateSavedState();
}
}
void UVREditorModeBase::Shutdown()
{
// @todo vreditor urgent: Disable global editor hacks for VR Editor mode
GEnableVREditorHacks = false;
Super::Shutdown();
}
namespace UE::VREditor::Private
{
TSharedPtr<SLevelViewport> TryGetActiveViewport()
{
const TSharedRef<ILevelEditor>& LevelEditor = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor").GetFirstLevelEditor().ToSharedRef();
TSharedPtr<IAssetViewport> ActiveViewport = LevelEditor->GetActiveViewportInterface();
if (!ActiveViewport)
{
return nullptr;
}
return StaticCastSharedRef<SLevelViewport>(ActiveViewport->AsWidget());
}
}
void UVREditorModeBase::Enter()
{
using namespace UE::VREditor::Private;
TSharedPtr<SLevelViewport> ActiveLevelViewport = TryGetActiveViewport();
if (ActiveLevelViewport && ActiveLevelViewport->GetCommandList() && FLevelViewportCommands::Get().SetDefaultViewportType)
{
ActiveLevelViewport->GetCommandList()->TryExecuteAction(
FLevelViewportCommands::Get().SetDefaultViewportType.ToSharedRef());
// If the active viewport was e.g. a cinematic viewport, changing it
// back to default recreated it and our pointer might be stale.
ActiveLevelViewport = TryGetActiveViewport();
}
if (!ensure(ActiveLevelViewport))
{
return;
}
ActiveLevelViewport->RemoveAllPreviews(true);
StartViewport(ActiveLevelViewport);
OnVRModeEntryCompleteEvent.Broadcast();
}
void UVREditorModeBase::Exit(bool bShouldDisableStereo)
{
CloseViewport(bShouldDisableStereo);
VREditorLevelViewportWeakPtr.Reset();
}
const SLevelViewport& UVREditorModeBase::GetLevelViewportPossessedForVR() const
{
check(VREditorLevelViewportWeakPtr.IsValid());
return *VREditorLevelViewportWeakPtr.Pin();
}
SLevelViewport& UVREditorModeBase::GetLevelViewportPossessedForVR()
{
check(VREditorLevelViewportWeakPtr.IsValid());
return *VREditorLevelViewportWeakPtr.Pin();
}
void UVREditorModeBase::StartViewport(TSharedPtr<SLevelViewport> Viewport)
{
check(Viewport);
VREditorLevelViewportWeakPtr = Viewport;
FBaseSavedEditorState& SavedEditorState = SavedEditorStateChecked();
FLevelEditorViewportClient& VRViewportClient = Viewport->GetLevelViewportClient();
// Make sure we are in perspective mode
// @todo vreditor: We should never allow ortho switching while in VR
SavedEditorState.ViewportType = VRViewportClient.GetViewportType();
VRViewportClient.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
VRViewportClient.SetVREditView(true);
SavedEditorState.bRealTime = VRViewportClient.IsRealtime();
VRViewportClient.SetRealtime(true);
SavedEditorState.ShowFlags = VRViewportClient.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;
SetNearClipPlaneGlobals(VREd::DefaultVRNearClipPlane->GetFloat());
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::LocalFloor);
}
// 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)
{
if (!Viewport->IsImmersive())
{
// Switch to immersive mode
const bool bWantImmersive = true;
const bool bAllowAnimation = false;
Viewport->MakeImmersive(bWantImmersive, bAllowAnimation);
}
EnableStereo();
}
}
void UVREditorModeBase::CloseViewport( const bool bShouldDisableStereo )
{
FBaseSavedEditorState& SavedEditorState = SavedEditorStateChecked();
if (bActuallyUsingVR && bShouldDisableStereo)
{
DisableStereo();
}
if (TSharedPtr<SLevelViewport> VREditorLevelViewport = GetVrLevelViewport())
{
if ( bShouldDisableStereo && bActuallyUsingVR )
{
// Leave immersive mode
const bool bWantImmersive = false;
const bool bAllowAnimation = false;
VREditorLevelViewport->MakeImmersive(bWantImmersive, bAllowAnimation);
}
FLevelEditorViewportClient& VRViewportClient = VREditorLevelViewport->GetLevelViewportClient();
// Restore settings that we changed on the viewport
VRViewportClient.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);
VRViewportClient.SetRealtime(SavedEditorState.bRealTime);
SetNearClipPlaneGlobals(SavedEditorState.NearClipPlane);
GAreScreenMessagesEnabled = SavedEditorState.bOnScreenMessages;
if (bActuallyUsingVR)
{
GEngine->XRSystem->SetTrackingOrigin(SavedEditorState.TrackingOrigin);
}
// Restore WorldToMeters
const float DefaultWorldToMeters = VREd::DefaultWorldToMeters->GetFloat();
GetWorld()->GetWorldSettings()->WorldToMeters = DefaultWorldToMeters != 0.0f ? DefaultWorldToMeters : SavedEditorState.WorldToMetersScale;
ENGINE_API extern float GNewWorldToMetersScale;
GNewWorldToMetersScale = 0.0f;
}
}
void UVREditorModeBase::StereoViewportSetup(TSharedRef<SLevelViewport> Viewport)
{
Viewport->EnableStereoRendering(true);
Viewport->SetRenderDirectlyToWindow(true);
if (TSharedPtr<SViewport> ViewportWidget = Viewport->GetViewportWidget().Pin(); ensure(ViewportWidget))
{
TSharedRef<SViewport> ViewportWidgetRef = ViewportWidget.ToSharedRef();
TSharedPtr<SWindow> ParentWindow = FSlateApplication::Get().FindWidgetWindow(ViewportWidgetRef);
TSharedPtr<ISlateViewport> SlateViewport = ViewportWidgetRef->GetViewportInterface().Pin();
if (ensure(ParentWindow) && ensure(SlateViewport))
{
// It is important to ensure that our containing SWindow is associated with our
// ISlateViewport, so FSlateRHIRenderer will invoke the UseSeparateRenderTarget path
// to correctly interoperate with the XR swapchain.
ParentWindow->SetViewport(SlateViewport.ToSharedRef());
}
}
}
void UVREditorModeBase::StereoViewportShutdown(TSharedRef<SLevelViewport> Viewport)
{
Viewport->EnableStereoRendering(false);
Viewport->SetRenderDirectlyToWindow(false);
if (TSharedPtr<SViewport> ViewportWidget = Viewport->GetViewportWidget().Pin(); ensure(ViewportWidget))
{
TSharedRef<SViewport> ViewportWidgetRef = ViewportWidget.ToSharedRef();
TSharedPtr<SWindow> ParentWindow = FSlateApplication::Get().FindWidgetWindow(ViewportWidgetRef);
TSharedPtr<ISlateViewport> SlateViewport = ViewportWidgetRef->GetViewportInterface().Pin();
if (ensure(ParentWindow) && ensure(SlateViewport))
{
// It's also important to ensure that we remove the ISlateViewport association when
// we're done with stereo rendering, or else multiple SWindows might end up associated
// with the same viewport (e.g. undocked/floating/orphan viewport), causing artifacts.
ParentWindow->UnsetViewport(SlateViewport.ToSharedRef());
}
}
}
void UVREditorModeBase::EnableStereo()
{
if (TSharedPtr<SLevelViewport> Viewport = GetVrLevelViewport(); ensure(Viewport))
{
StereoViewportSetup(Viewport.ToSharedRef());
}
if (GEngine->XRSystem)
{
GEngine->StereoRenderingDevice->EnableStereo(true);
}
}
void UVREditorModeBase::DisableStereo()
{
if (GEngine->XRSystem)
{
GEngine->StereoRenderingDevice->EnableStereo(false);
}
if (TSharedPtr<SLevelViewport> Viewport = GetVrLevelViewport(); ensure(Viewport))
{
StereoViewportShutdown(Viewport.ToSharedRef());
}
}