Files
UnrealEngineUWP/Engine/Source/Editor/VREditor/Private/VREditorMode.cpp
christopher fiala 3c60313f53 Fix LWC float truncation warnings for VREditor, AugmentedReality, and MRMesh modules.
Primarily float casts or FVector2f conversions for calculations that are taking place in local space or screen space anyway.

#jira UE-166461
#rb jeff.fisher

[CL 25963198 by christopher fiala in ue5-main branch]
2023-06-13 16:49:58 -04: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