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

1101 lines
38 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 "UI/VREditorUISystem.h"
#include "VIBaseTransformGizmo.h"
#include "ViewportWorldInteraction.h"
#include "VREditorPlacement.h"
#include "VREditorAvatarActor.h"
#include "Teleporter/VREditorTeleporter.h"
#include "Teleporter/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 "IHeadMountedDisplay.h"
#include "IXRTrackingSystem.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 "IMotionController.h"
#include "UI/VREditorFloatingUI.h"
#include "AssetEditorViewportLayout.h"
#include "LevelViewportActions.h"
#define LOCTEXT_NAMESPACE "VREditorMode"
namespace VREd
{
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" ) );
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 ),
InteractorClass( UVREditorInteractor::StaticClass() ),
TeleporterClass( AVREditorTeleporter::StaticClass() ),
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()
{
Super::Init();
bIsFullyInitialized = false;
bWantsToExitMode = false;
AppTimeModeEntered = FTimespan::FromSeconds( FApp::GetCurrentTime() );
// 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);
// Add viewport world interaction to the collection if not already there
WorldInteraction = Cast<UViewportWorldInteraction>(Collection->FindExtension(UViewportWorldInteraction::StaticClass()));
if (WorldInteraction == nullptr)
{
WorldInteraction = NewObject<UViewportWorldInteraction>(Collection);
check(WorldInteraction != nullptr);
Collection->AddExtension(WorldInteraction);
bAddedViewportWorldInteractionExtension = true;
}
else
{
WorldInteraction->UseVWInteractions();
}
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;
Super::Shutdown();
}
void UVREditorMode::AllocateInteractors()
{
class UVREditorInteractor* LeftHandInteractor = nullptr;
class UVREditorInteractor* RightHandInteractor = nullptr;
InteractorClass.LoadSynchronous();
if (InteractorClass.IsValid())
{
LeftHandInteractor = NewObject<UVREditorInteractor>(GetTransientPackage(), InteractorClass.Get());
RightHandInteractor = NewObject<UVREditorInteractor>(GetTransientPackage(), InteractorClass.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(IMotionController::LeftHandSourceId );
RightHandInteractor->SetControllerHandSide( IMotionController::RightHandSourceId );
for (UVREditorInteractor* Interactor : Interactors)
{
Interactor->Init( this );
WorldInteraction->AddInteractor( Interactor );
}
WorldInteraction->PairInteractors( LeftHandInteractor, RightHandInteractor );
}
void UVREditorMode::Enter()
{
BeginEntry();
SetupSubsystems();
FinishEntry();
}
namespace UE::VREditor::Private
{
// Defined in VREditorModeBase.cpp.
TSharedPtr<SLevelViewport> TryGetActiveViewport();
}
void UVREditorMode::BeginEntry()
{
using namespace UE::VREditor::Private;
FSavedEditorState& SavedEditorState = static_cast<FSavedEditorState&>(SavedEditorStateChecked());
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 = TryGetActiveViewport();
if (ExistingActiveLevelViewport && ExistingActiveLevelViewport->GetCommandList() && FLevelViewportCommands::Get().SetDefaultViewportType)
{
ExistingActiveLevelViewport->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.
ExistingActiveLevelViewport = TryGetActiveViewport();
}
if (!ensure(ExistingActiveLevelViewport))
{
return;
}
ExistingActiveLevelViewport->RemoveAllPreviews(true);
StartViewport(ExistingActiveLevelViewport);
if (WorldInteraction != nullptr)
{
WorldInteraction->SetDefaultOptionalViewportClient(ExistingActiveLevelViewport->GetViewportClient());
}
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);
}
}
// 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
TeleporterClass.LoadSynchronous();
if (TeleporterClass.IsValid())
{
TeleportActor = CastChecked<AVREditorTeleporter>( SpawnTransientSceneActor(TeleporterClass.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)
{
const FSavedEditorState& SavedEditorState = static_cast<const FSavedEditorState&>(SavedEditorStateChecked());
{
if (TSharedPtr<SLevelViewport> VrViewport = GetVrLevelViewport())
{
VrViewport->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);
}
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);
}
UEditorWorldExtensionCollection* Collection = GetOwningCollection();
check(Collection != nullptr);
if (bAddedViewportWorldInteractionExtension)
{
Collection->RemoveExtension(WorldInteraction);
bAddedViewportWorldInteractionExtension = false;
}
else
{
WorldInteraction->UseLegacyInteractions();
}
WorldInteraction = nullptr;
}
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;
}
const FSavedEditorState& SavedEditorState = static_cast<const FSavedEditorState&>(SavedEditorStateChecked());
//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 = (float) ( LastRoomSpaceHeadToWorld.GetLocation() / LastWorldScaleFactor - RoomSpaceHeadToWorld.GetLocation() / WorldScaleFactor ).Size() / DeltaTime;
const float YawVelocity = (float) FMath::Abs( FMath::FindDeltaAngleDegrees( LastRoomSpaceHeadToWorld.GetRotation().Rotator().Yaw, RoomSpaceHeadToWorld.GetRotation().Rotator().Yaw ) ) / DeltaTime;
const float PitchVelocity = (float) FMath::Abs( FMath::FindDeltaAngleDegrees( LastRoomSpaceHeadToWorld.GetRotation().Rotator().Pitch, RoomSpaceHeadToWorld.GetRotation().Rotator().Pitch ) ) / DeltaTime;
const float RollVelocity = (float) 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;
}
bool UVREditorMode::GetLaserForHand(EControllerHand InHand, FVector& OutLaserStart, FVector& OutLaserEnd) const
{
if (UVREditorInteractor* Interactor = GetHandInteractor(InHand))
{
AVREditorTeleporter* Teleporter = Interactor->GetTeleportActor();
const bool bHasLaser =
Interactor->GetControllerType() == EControllerType::AssistingLaser
|| Interactor->GetControllerType() == EControllerType::Laser
|| (Teleporter && Teleporter->IsAiming());
if (bHasLaser)
{
OutLaserStart = Interactor->GetLaserStart();
OutLaserEnd = Interactor->GetLaserEnd();
return true;
}
}
return 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);
}
void UVREditorMode::SetGameView(bool bGameView)
{
if (TSharedPtr<SLevelViewport> Viewport = VREditorLevelViewportWeakPtr.Pin())
{
// We can't actually set the viewport to game view, because AVREditorAvatarActor::IsEditorOnly is
// overridden to return true, and so its owned components (including the interactors) get hidden.
// However, clearing the "editor" flag turns out to get us close to what we'd want.
FLevelEditorViewportClient& ViewportClient = Viewport->GetLevelViewportClient();
ViewportClient.EngineShowFlags.SetEditor(!bGameView);
}
}
bool UVREditorMode::IsInGameView() const
{
if (TSharedPtr<SLevelViewport> Viewport = VREditorLevelViewportWeakPtr.Pin())
{
FLevelEditorViewportClient& ViewportClient = Viewport->GetLevelViewportClient();
return ViewportClient.EngineShowFlags.Editor == 0;
}
return false;
}
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 = (float) ( 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 = (float) 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
{
const FSavedEditorState& SavedEditorState = static_cast<const FSavedEditorState&>(SavedEditorStateChecked());
return SavedEditorState;
}
void UVREditorMode::SaveSequencerSettings(bool bInKeyAllEnabled, EAutoChangeMode InAutoChangeMode, const class USequencerSettings& InSequencerSettings)
{
FSavedEditorState& SavedEditorState = static_cast<FSavedEditorState&>(SavedEditorStateChecked());
SavedEditorState.bKeyAllEnabled = bInKeyAllEnabled;
SavedEditorState.AutoChangeMode = InAutoChangeMode;
}
void UVREditorMode::TransitionWorld(UWorld* NewWorld, EEditorWorldExtensionTransitionState TransitionState)
{
Super::TransitionWorld(NewWorld, TransitionState);
UISystem->TransitionWorld(NewWorld, TransitionState);
}
void UVREditorMode::RestoreWorldToMeters()
{
const FSavedEditorState& SavedEditorState = static_cast<const FSavedEditorState&>(SavedEditorStateChecked());
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();
}
// static
void UVREditorMode::ToggleDebugMode()
{
UVREditorMode::bDebugModeEnabled = !UVREditorMode::bDebugModeEnabled;
IVREditorModule& VREditorModule = IVREditorModule::Get();
PRAGMA_DISABLE_DEPRECATION_WARNINGS
UVREditorMode* VRMode = VREditorModule.GetVRMode();
PRAGMA_ENABLE_DEPRECATION_WARNINGS
if (VRMode != nullptr)
{
VRMode->OnToggleDebugMode().Broadcast(UVREditorMode::bDebugModeEnabled);
}
}
// static
bool UVREditorMode::IsDebugModeEnabled()
{
return UVREditorMode::bDebugModeEnabled;
}
class AVREditorTeleporter* UVREditorMode::GetTeleportActor()
{
return TeleportActor;
}
#undef LOCTEXT_NAMESPACE