You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rnx #Jira UE-97664 #rb matt.kuhlenschmidt [CL 14269170 by brooke hubert in ue5-main branch]
1209 lines
44 KiB
C++
1209 lines
44 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 )
|
|
{
|
|
}
|
|
|
|
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()
|
|
{
|
|
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"));
|
|
LevelEditorModule.GetLevelEditorTabManager()->TryInvokeTab(FTabId("Sequencer"))->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 );
|
|
}
|
|
|
|
// Setup sub systems
|
|
{
|
|
// 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 );
|
|
}
|
|
}
|
|
|
|
|
|
/** This will make sure this is not ticking after the editor has been closed. */
|
|
GEditor->OnEditorClose().AddUObject( this, &UVREditorMode::OnEditorClosed );
|
|
|
|
bFirstTick = true;
|
|
SetActive(true);
|
|
}
|
|
|
|
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->MarkPendingKill();
|
|
UISystem = nullptr;
|
|
}
|
|
|
|
if( PlacementSystem != nullptr )
|
|
{
|
|
PlacementSystem->Shutdown();
|
|
PlacementSystem->MarkPendingKill();
|
|
PlacementSystem = nullptr;
|
|
}
|
|
|
|
if( TeleportActor != nullptr )
|
|
{
|
|
DestroyTransientActor( TeleportActor );
|
|
TeleportActor = nullptr;
|
|
}
|
|
|
|
if( AutoScalerSystem != nullptr )
|
|
{
|
|
AutoScalerSystem->Shutdown();
|
|
AutoScalerSystem->MarkPendingKill();
|
|
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->MarkPendingKill();
|
|
}
|
|
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 );
|
|
|
|
if (GEditor->bIsSimulatingInEditor)
|
|
{
|
|
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
|
|
{
|
|
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
|