Android build settings + metaxr

This commit is contained in:
2025-05-14 14:00:02 +03:00
parent 6a2bb7475e
commit d5aa21f55c
594 changed files with 200530 additions and 2 deletions

View File

@@ -0,0 +1,77 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
namespace UnrealBuildTool.Rules
{
public class OculusXRMR : ModuleRules
{
public OculusXRMR(ReadOnlyTargetRules Target) : base(Target)
{
bUseUnity = true;
PrivateIncludePathModuleNames.AddRange(
new string[]
{
"InputDevice", // For IInputDevice.h
"HeadMountedDisplay", // For IMotionController.h
"ImageWrapper",
"Engine"
});
PrivateDependencyModuleNames.AddRange(
new string[]
{
"Core",
"CoreUObject",
"Engine",
"InputCore",
"Slate",
"SlateCore",
"RHI",
"VulkanRHI",
"RenderCore",
"MediaAssets",
"HeadMountedDisplay",
"OculusXRHMD",
"OVRPluginXR",
});
if (Target.Version.MajorVersion > 5 || (Target.Version.MajorVersion == 5 && Target.Version.MinorVersion >= 3))
{
PrivateDependencyModuleNames.AddRange(
new string[]
{
"XRBase",
});
}
PrivateIncludePaths.AddRange(
new string[] {
"OculusXRHMD/Private",
"OculusXRInput/Private",
});
PublicIncludePaths.AddRange(
new string[] {
"Runtime/Renderer/Private",
"Runtime/Engine/Classes/Components",
"Runtime/MediaAssets/Private",
});
if (Target.Platform == UnrealTargetPlatform.Win64)
{
PublicDelayLoadDLLs.Add("OVRPluginXR.dll");
}
if (Target.Platform == UnrealTargetPlatform.Android)
{
AddEngineThirdPartyPrivateStaticDependencies(Target, "Vulkan");
}
if (Target.bBuildEditor == true)
{
PrivateDependencyModuleNames.Add("UnrealEd");
}
}
}
}

View File

@@ -0,0 +1,177 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#include "OculusXRMRFunctionLibrary.h"
#include "OculusXRMRPrivate.h"
#include "OculusXRMRModule.h"
#include "OculusXRMR_CastingCameraActor.h"
#include "OculusXRMR_State.h"
#include "OculusXRHMD.h"
#include "OculusXRHMDPrivate.h"
#include "IHeadMountedDisplay.h"
#include "IXRTrackingSystem.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/PlayerController.h"
//-------------------------------------------------------------------------------------------------
// UOculusXRFunctionLibrary
//-------------------------------------------------------------------------------------------------
UOculusXRMRFunctionLibrary::UOculusXRMRFunctionLibrary(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void UOculusXRMRFunctionLibrary::GetAllTrackedCamera(TArray<FOculusXRTrackedCamera>& TrackedCameras, bool bCalibratedOnly)
{
TrackedCameras.Empty();
if (!FOculusXRMRModule::IsAvailable() || !FOculusXRMRModule::Get().IsInitialized())
{
UE_LOG(LogMR, Error, TEXT("OculusXRMR not available"));
return;
}
if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized() == ovrpBool_False)
{
UE_LOG(LogMR, Error, TEXT("OVRPlugin not initialized"));
return;
}
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().UpdateExternalCamera()))
{
UE_LOG(LogMR, Error, TEXT("FOculusXRHMDModule::GetPluginWrapper().UpdateExternalCamera failure"));
return;
}
int cameraCount = 0;
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetExternalCameraCount(&cameraCount)))
{
UE_LOG(LogMR, Log, TEXT("FOculusXRHMDModule::GetPluginWrapper().GetExternalCameraCount failure"));
return;
}
for (int i = 0; i < cameraCount; ++i)
{
char cameraName[OVRP_EXTERNAL_CAMERA_NAME_SIZE];
ovrpCameraIntrinsics cameraIntrinsics;
ovrpCameraExtrinsics cameraExtrinsics;
FOculusXRHMDModule::GetPluginWrapper().GetExternalCameraName(i, cameraName);
FOculusXRHMDModule::GetPluginWrapper().GetExternalCameraIntrinsics(i, &cameraIntrinsics);
FOculusXRHMDModule::GetPluginWrapper().GetExternalCameraExtrinsics(i, &cameraExtrinsics);
if ((bCalibratedOnly == false || cameraExtrinsics.CameraStatus == ovrpCameraStatus_Calibrated) && cameraIntrinsics.IsValid && cameraExtrinsics.IsValid)
{
FOculusXRTrackedCamera camera;
camera.Index = i;
camera.Name = cameraName;
camera.FieldOfView = FMath::RadiansToDegrees(FMath::Atan(cameraIntrinsics.FOVPort.LeftTan) + FMath::Atan(cameraIntrinsics.FOVPort.RightTan));
camera.SizeX = cameraIntrinsics.ImageSensorPixelResolution.w;
camera.SizeY = cameraIntrinsics.ImageSensorPixelResolution.h;
camera.AttachedTrackedDevice = OculusXRHMD::ToEOculusXRTrackedDeviceType(cameraExtrinsics.AttachedToNode);
OculusXRHMD::FPose Pose;
static_cast<OculusXRHMD::FOculusXRHMD*>(GEngine->XRSystem.Get())->ConvertPose(cameraExtrinsics.RelativePose, Pose);
camera.CalibratedRotation = Pose.Orientation.Rotator();
camera.CalibratedOffset = Pose.Position;
camera.UserRotation = FRotator::ZeroRotator;
camera.UserOffset = FVector::ZeroVector;
TrackedCameras.Add(camera);
}
}
}
TSharedPtr<class IXRTrackingSystem, ESPMode::ThreadSafe> UOculusXRMRFunctionLibrary::GetTrackingSystem()
{
#if OCULUS_HMD_SUPPORTED_PLATFORMS
if (GEngine && GEngine->XRSystem.IsValid())
{
return GEngine->XRSystem;
}
#endif
return nullptr;
}
bool UOculusXRMRFunctionLibrary::GetTrackingReferenceLocationAndRotationInWorldSpace(USceneComponent* TrackingReferenceComponent, FVector& TRLocation, FRotator& TRRotation)
{
if (!TrackingReferenceComponent)
{
APlayerController* PlayerController = GWorld->GetFirstPlayerController();
if (!PlayerController)
{
return false;
}
APawn* Pawn = PlayerController->GetPawn();
if (!Pawn)
{
return false;
}
TRLocation = Pawn->GetActorLocation();
TRRotation = Pawn->GetActorRotation();
return true;
}
else
{
TRLocation = TrackingReferenceComponent->GetComponentLocation();
TRRotation = TrackingReferenceComponent->GetComponentRotation();
return true;
}
}
UOculusXRMR_Settings* UOculusXRMRFunctionLibrary::GetOculusXRMRSettings()
{
UOculusXRMR_Settings* Settings = nullptr;
if (FOculusXRMRModule::IsAvailable())
{
Settings = FOculusXRMRModule::Get().GetMRSettings();
}
return Settings;
}
USceneComponent* UOculusXRMRFunctionLibrary::GetTrackingReferenceComponent()
{
USceneComponent* TrackingRef = nullptr;
if (FOculusXRMRModule::IsAvailable())
{
TrackingRef = FOculusXRMRModule::Get().GetMRState()->TrackingReferenceComponent;
}
return TrackingRef;
}
bool UOculusXRMRFunctionLibrary::SetTrackingReferenceComponent(USceneComponent* Component)
{
if (FOculusXRMRModule::IsAvailable())
{
FOculusXRMRModule::Get().GetMRState()->TrackingReferenceComponent = Component;
return true;
}
return false;
}
float UOculusXRMRFunctionLibrary::GetMrcScalingFactor()
{
if (FOculusXRMRModule::IsAvailable())
{
return FOculusXRMRModule::Get().GetMRState()->ScalingFactor;
}
return 0.0;
}
bool UOculusXRMRFunctionLibrary::SetMrcScalingFactor(float ScalingFactor)
{
if (FOculusXRMRModule::IsAvailable() && ScalingFactor > 0.0f)
{
FOculusXRMRModule::Get().GetMRState()->ScalingFactor = ScalingFactor;
return true;
}
return false;
}
bool UOculusXRMRFunctionLibrary::IsMrcEnabled()
{
return FOculusXRMRModule::IsAvailable() && FOculusXRMRModule::Get().IsInitialized();
}
bool UOculusXRMRFunctionLibrary::IsMrcActive()
{
return FOculusXRMRModule::IsAvailable() && FOculusXRMRModule::Get().IsActive();
}

View File

@@ -0,0 +1,541 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#include "OculusXRMRModule.h"
#include "Engine/Engine.h"
#include "ISpectatorScreenController.h"
#include "IXRTrackingSystem.h"
#include "StereoRendering.h"
#include "StereoRenderTargetManager.h"
#include "SceneCaptureComponent2D.h"
#include "Engine/TextureRenderTarget2D.h"
#include "EngineUtils.h"
#include "PostProcess/SceneRenderTargets.h"
#include "Kismet/GameplayStatics.h"
#include "OculusXRHMDModule.h"
#include "OculusXRHMD.h"
#include "OculusXRMRFunctionLibrary.h"
#include "OculusXRMRPrivate.h"
#include "OculusXRMR_Settings.h"
#include "OculusXRMR_State.h"
#include "OculusXRMR_CastingCameraActor.h"
#include "AudioDevice.h"
#if PLATFORM_ANDROID
#include "IVulkanDynamicRHI.h"
#endif
#if WITH_EDITOR
#include "Editor.h" // for FEditorDelegates::PostPIEStarted
#endif
#define LOCTEXT_NAMESPACE "OculusXRMR"
FOculusXRMRModule::FOculusXRMRModule()
: bInitialized(false)
, MRSettings(nullptr)
, MRState(nullptr)
, MRActor(nullptr)
, CurrentWorld(nullptr)
, WorldAddedEventBinding()
, WorldDestroyedEventBinding()
, WorldLoadEventBinding()
#if PLATFORM_ANDROID
, bActivated(false)
, InitialWorldAddedEventBinding()
, InitialWorldLoadEventBinding()
, PreWorldTickEventBinding()
#endif
#if WITH_EDITOR
, PieBeginEventBinding()
, PieStartedEventBinding()
, PieEndedEventBinding()
#endif
{
}
FOculusXRMRModule::~FOculusXRMRModule()
{
}
void FOculusXRMRModule::StartupModule()
{
#if OCULUS_MR_SUPPORTED_PLATFORMS
#if PLATFORM_WINDOWS
const TCHAR* CmdLine = FCommandLine::Get();
const bool bAutoOpenFromParams = FParse::Param(CmdLine, TEXT("mixedreality"));
if (bAutoOpenFromParams && FOculusXRHMDModule::Get().PreInit())
{
if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized())
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().InitializeMixedReality()))
{
InitMixedRealityCapture();
}
else
{
UE_LOG(LogMR, Error, TEXT("ovrp_InitializeMixedReality() failed"));
}
}
else
{
UE_LOG(LogMR, Error, TEXT("OVRPlugin has not been initialized"));
}
}
#elif PLATFORM_ANDROID
// On Android, FOculusXRHMDModule::GetPluginWrapper().Media_Initialize() needs OVRPlugin to be initialized first, so we should handle that when the world is created
if (GEngine)
{
InitialWorldAddedEventBinding = GEngine->OnWorldAdded().AddRaw(this, &FOculusXRMRModule::OnInitialWorldCreated);
}
InitialWorldLoadEventBinding = FCoreUObjectDelegates::PostLoadMapWithWorld.AddRaw(this, &FOculusXRMRModule::OnInitialWorldCreated);
#endif // PLATFORM_WINDOWS || PLATFORM_ANDROID
#endif // OCULUS_MR_SUPPORTED_PLATFORMS
}
void FOculusXRMRModule::ShutdownModule()
{
#if OCULUS_MR_SUPPORTED_PLATFORMS
if (bInitialized)
{
if (GEngine)
{
GEngine->OnWorldAdded().Remove(WorldAddedEventBinding);
GEngine->OnWorldDestroyed().Remove(WorldDestroyedEventBinding);
FCoreUObjectDelegates::PostLoadMapWithWorld.Remove(WorldLoadEventBinding);
#if WITH_EDITOR
FEditorDelegates::PostPIEStarted.Remove(PieStartedEventBinding);
FEditorDelegates::PrePIEEnded.Remove(PieEndedEventBinding);
#else
// Stop casting and close camera with module if it's the game
MRSettings->SetIsCasting(false);
#endif
}
#if PLATFORM_ANDROID
ovrpBool mediaInit = false;
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().Media_GetInitialized(&mediaInit)) && mediaInit == ovrpBool_True)
{
FOculusXRHMDModule::GetPluginWrapper().Media_Shutdown();
}
#endif
FOculusXRHMDModule::GetPluginWrapper().ShutdownMixedReality();
if (MRSettings->IsRooted())
{
MRSettings->RemoveFromRoot();
}
if (MRState->IsRooted())
{
MRState->RemoveFromRoot();
}
}
#if PLATFORM_ANDROID
if (InitialWorldAddedEventBinding.IsValid() && GEngine)
{
GEngine->OnWorldAdded().Remove(InitialWorldAddedEventBinding);
InitialWorldAddedEventBinding.Reset();
}
if (InitialWorldLoadEventBinding.IsValid())
{
FCoreUObjectDelegates::PostLoadMapWithWorld.Remove(InitialWorldLoadEventBinding);
InitialWorldLoadEventBinding.Reset();
}
#endif
#endif // OCULUS_MR_SUPPORTED_PLATFORMS
}
bool FOculusXRMRModule::IsActive()
{
bool bReturn = bInitialized && MRSettings && MRSettings->GetIsCasting();
#if PLATFORM_ANDROID
bReturn = bReturn && bActivated;
#endif
return bReturn;
}
UOculusXRMR_Settings* FOculusXRMRModule::GetMRSettings()
{
return MRSettings;
}
UOculusXRMR_State* FOculusXRMRModule::GetMRState()
{
return MRState;
}
void FOculusXRMRModule::OnWorldCreated(UWorld* NewWorld)
{
#if PLATFORM_WINDOWS
#if WITH_EDITORONLY_DATA
const bool bIsGameInst = !IsRunningCommandlet() && NewWorld->IsGameWorld();
if (bIsGameInst)
#endif
{
CurrentWorld = NewWorld;
SetupInGameCapture();
}
#endif
#if PLATFORM_ANDROID
CurrentWorld = NewWorld;
// Check MRC activation state initially when loading world
ChangeCaptureState();
// Poll MRC activation state for future changes
PreWorldTickEventBinding = FWorldDelegates::OnWorldPreActorTick.AddRaw(this, &FOculusXRMRModule::OnWorldTick);
#endif
}
void FOculusXRMRModule::OnWorldDestroyed(UWorld* NewWorld)
{
CurrentWorld = nullptr;
#if PLATFORM_ANDROID
if (PreWorldTickEventBinding.IsValid())
{
FWorldDelegates::OnWorldPreActorTick.Remove(PreWorldTickEventBinding);
PreWorldTickEventBinding.Reset();
}
#endif // PLATFORM_ANDROID
}
void FOculusXRMRModule::InitMixedRealityCapture()
{
bInitialized = true;
MRSettings = NewObject<UOculusXRMR_Settings>((UObject*)GetTransientPackage(), FName("OculusXRMR_Settings"), RF_MarkAsRootSet);
MRState = NewObject<UOculusXRMR_State>((UObject*)GetTransientPackage(), FName("OculusXRMR_State"), RF_MarkAsRootSet);
// Always bind the event handlers in case devs call them without MRC on
MRSettings->TrackedCameraIndexChangeDelegate.BindRaw(this, &FOculusXRMRModule::OnTrackedCameraIndexChanged);
MRSettings->CompositionMethodChangeDelegate.BindRaw(this, &FOculusXRMRModule::OnCompositionMethodChanged);
MRSettings->IsCastingChangeDelegate.BindRaw(this, &FOculusXRMRModule::OnIsCastingChanged);
ResetSettingsAndState();
WorldAddedEventBinding = GEngine->OnWorldAdded().AddRaw(this, &FOculusXRMRModule::OnWorldCreated);
WorldDestroyedEventBinding = GEngine->OnWorldDestroyed().AddRaw(this, &FOculusXRMRModule::OnWorldDestroyed);
WorldLoadEventBinding = FCoreUObjectDelegates::PostLoadMapWithWorld.AddRaw(this, &FOculusXRMRModule::OnWorldCreated);
#if WITH_EDITOR
// Bind events on PIE start/end to open/close camera
PieBeginEventBinding = FEditorDelegates::BeginPIE.AddRaw(this, &FOculusXRMRModule::OnPieBegin);
PieStartedEventBinding = FEditorDelegates::PostPIEStarted.AddRaw(this, &FOculusXRMRModule::OnPieStarted);
PieEndedEventBinding = FEditorDelegates::PrePIEEnded.AddRaw(this, &FOculusXRMRModule::OnPieEnded);
#else // WITH_EDITOR
// Start casting and open camera with the module if it's the game
MRSettings->SetIsCasting(true);
#endif // WITH_EDITOR
}
void FOculusXRMRModule::SetupExternalCamera()
{
using namespace OculusXRHMD;
if (!MRSettings->GetIsCasting())
{
return;
}
// Always request the MRC actor to handle a camera state change on its end
MRState->ChangeCameraStateRequested = true;
}
void FOculusXRMRModule::SetupInGameCapture()
{
// Don't do anything if we don't have a UWorld or if we are not casting
if (CurrentWorld == nullptr || !MRSettings->GetIsCasting())
{
return;
}
// Set the bind camera request to true
MRState->BindToTrackedCameraIndexRequested = true;
// Don't add another actor if there's already a MRC camera actor
for (TActorIterator<AOculusXRMR_CastingCameraActor> ActorIt(CurrentWorld); ActorIt; ++ActorIt)
{
if (IsValidChecked(*ActorIt) && !ActorIt->IsUnreachable() && ActorIt->IsValidLowLevel())
{
MRActor = *ActorIt;
return;
}
}
// Spawn an MRC camera actor if one wasn't already there
MRActor = CurrentWorld->SpawnActorDeferred<AOculusXRMR_CastingCameraActor>(AOculusXRMR_CastingCameraActor::StaticClass(), FTransform::Identity);
MRActor->InitializeStates(MRSettings, MRState);
UGameplayStatics::FinishSpawningActor(MRActor, FTransform::Identity);
}
void FOculusXRMRModule::CloseInGameCapture()
{
// Destory actor and close the camera when we turn MRC off
if (MRActor != nullptr && MRActor->GetWorld() != nullptr)
{
MRActor->Destroy();
MRActor = nullptr;
}
}
void FOculusXRMRModule::ResetSettingsAndState()
{
// Reset MR State
MRState->TrackedCamera = FOculusXRTrackedCamera();
MRState->TrackingReferenceComponent = nullptr;
MRState->ChangeCameraStateRequested = false;
MRState->BindToTrackedCameraIndexRequested = false;
// Reset MR Settings
const bool bAutoOpenInExternalComposition = FParse::Param(FCommandLine::Get(), TEXT("externalcomposition"));
MRSettings->BindToTrackedCameraIndexIfAvailable(0);
MRSettings->LoadFromIni();
// Save right after load to write defaults to the config if they weren't already there
MRSettings->SaveToIni();
if (bAutoOpenInExternalComposition)
{
MRSettings->CompositionMethod = EOculusXRMR_CompositionMethod::ExternalComposition;
}
}
#if PLATFORM_ANDROID
void FOculusXRMRModule::ChangeCaptureState()
{
ovrpBool activated;
// Set up or close in-game capture when activation state changes
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().Media_Update()) && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().Media_IsMrcActivated(&activated)) && activated == ovrpBool_True)
{
if (!bActivated)
{
UE_LOG(LogMR, Log, TEXT("Activating MR Capture"))
bActivated = true;
// UE resizes the main scene color and depth targets to the maximum dimensions of all rendertargets,
// which causes rendering issues if it doesn't match the compositor-allocated eye textures. This is
// a hacky fix by making sure that the scene capture rendertarget is no larger than the eye.
int frameWidth;
int frameHeight;
FOculusXRHMDModule::GetPluginWrapper().Media_GetMrcFrameSize(&frameWidth, &frameHeight);
uint32 maxWidth = frameWidth / 2;
uint32 maxHeight = frameHeight;
IStereoRenderTargetManager* const StereoRenderTargetManager = GEngine->StereoRenderingDevice->GetRenderTargetManager();
if (StereoRenderTargetManager)
{
#ifdef WITH_OCULUS_BRANCH
StereoRenderTargetManager->CalculateRenderTargetSize(maxWidth, maxHeight);
#else // WITH_OCULUS_BRANCH
StereoRenderTargetManager->CalculateRenderTargetSize(*(FViewport*)GEngine->GameViewport->GetGameViewport(), maxWidth, maxHeight);
#endif // WITH_OCULUS_BRANCH
}
maxWidth *= 2;
frameWidth = frameWidth > maxWidth ? maxWidth : frameWidth;
frameHeight = frameHeight > maxHeight ? maxHeight : frameHeight;
FOculusXRHMDModule::GetPluginWrapper().Media_SetMrcFrameSize(frameWidth, frameHeight);
UE_LOG(LogMR, Log, TEXT("MRC Frame width: %d height %d"), frameWidth, frameHeight);
SetupInGameCapture();
}
}
else
{
if (bActivated)
{
UE_LOG(LogMR, Log, TEXT("Deactivating MR Capture"))
bActivated = false;
CloseInGameCapture();
}
}
}
void FOculusXRMRModule::OnWorldTick(UWorld* World, ELevelTick Tick, float Delta)
{
// Poll MRC activation state
if (CurrentWorld && World == CurrentWorld)
{
ChangeCaptureState();
}
}
void FOculusXRMRModule::OnInitialWorldCreated(UWorld* NewWorld)
{
// Remove the initial world load handlers
if (InitialWorldAddedEventBinding.IsValid())
{
GEngine->OnWorldAdded().Remove(InitialWorldAddedEventBinding);
InitialWorldAddedEventBinding.Reset();
}
if (InitialWorldLoadEventBinding.IsValid())
{
FCoreUObjectDelegates::PostLoadMapWithWorld.Remove(InitialWorldLoadEventBinding);
InitialWorldLoadEventBinding.Reset();
}
// Initialize and check if MRC is enabled
if (FOculusXRHMDModule::Get().PreInit())
{
if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized())
{
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().InitializeMixedReality()))
{
ovrpBool mrcEnabled;
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().Media_Initialize()))
{
UE_LOG(LogMR, Log, TEXT("MRC Initialized"));
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().Media_IsMrcEnabled(&mrcEnabled)) && mrcEnabled == ovrpBool_True)
{
UE_LOG(LogMR, Log, TEXT("MRC Enabled"));
// Find a free queue index for vulkan
if (RHIGetInterfaceType() == ERHIInterfaceType::Vulkan)
{
unsigned int queueIndex = 0;
ExecuteOnRenderThread([&queueIndex]() {
ExecuteOnRHIThread([&queueIndex]() {
const uint32 GraphicsQueueIndex = GetIVulkanDynamicRHI()->RHIGetGraphicsQueueIndex();
if (GraphicsQueueIndex == queueIndex)
{
++queueIndex;
}
});
});
FOculusXRHMDModule::GetPluginWrapper().Media_SetAvailableQueueIndexVulkan(queueIndex);
}
FOculusXRHMDModule::GetPluginWrapper().Media_SetMrcInputVideoBufferType(ovrpMediaInputVideoBufferType_TextureHandle);
FAudioDeviceHandle AudioDevice = FAudioDevice::GetMainAudioDevice();
if (AudioDevice.GetAudioDevice())
{
float SampleRate = AudioDevice->GetSampleRate();
FOculusXRHMDModule::GetPluginWrapper().Media_SetMrcAudioSampleRate((int)SampleRate);
}
InitMixedRealityCapture();
OnWorldCreated(NewWorld);
}
else
{
// Shut down if MRC not enabled or the media couldn't be enabled
FOculusXRHMDModule::GetPluginWrapper().Media_Shutdown();
FOculusXRHMDModule::GetPluginWrapper().ShutdownMixedReality();
}
}
else
{
// Shut down if MRC not enabled or the media couldn't be enabled
FOculusXRHMDModule::GetPluginWrapper().ShutdownMixedReality();
}
}
else
{
UE_LOG(LogMR, Error, TEXT("ovrp_InitializeMixedReality() failed"));
}
}
else
{
UE_LOG(LogMR, Error, TEXT("OVRPlugin has not been initialized"));
}
}
}
#endif
void FOculusXRMRModule::OnTrackedCameraIndexChanged(int OldVal, int NewVal)
{
if (OldVal == NewVal)
{
return;
}
MRState->BindToTrackedCameraIndexRequested = true;
}
void FOculusXRMRModule::OnCompositionMethodChanged(EOculusXRMR_CompositionMethod OldVal, EOculusXRMR_CompositionMethod NewVal)
{
if (OldVal == NewVal)
{
return;
}
SetupExternalCamera();
}
void FOculusXRMRModule::OnIsCastingChanged(bool OldVal, bool NewVal)
{
if (OldVal == NewVal)
{
return;
}
if (NewVal == true)
{
#if PLATFORM_ANDROID
FOculusXRHMDModule::GetPluginWrapper().Media_SetMrcActivationMode(ovrpMediaMrcActivationMode_Automatic);
#endif
// Initialize everything again if we turn MRC on
SetupExternalCamera();
SetupInGameCapture();
}
else
{
#if PLATFORM_ANDROID
FOculusXRHMDModule::GetPluginWrapper().Media_SetMrcActivationMode(ovrpMediaMrcActivationMode_Disabled);
#endif
CloseInGameCapture();
}
}
void FOculusXRMRModule::OnUseDynamicLightingChanged(bool OldVal, bool NewVal)
{
if (OldVal == NewVal)
{
return;
}
SetupExternalCamera();
}
void FOculusXRMRModule::OnDepthQualityChanged(EOculusXRMR_DepthQuality OldVal, EOculusXRMR_DepthQuality NewVal)
{
if (OldVal == NewVal)
{
return;
}
SetupExternalCamera();
}
#if WITH_EDITOR
void FOculusXRMRModule::OnPieBegin(bool bIsSimulating)
{
// Reset all the parameters and start casting when PIE starts but before the game is initialized
if (!bIsSimulating)
{
ResetSettingsAndState();
// Always start casting with PIE (since this can only be reached if the command line param is on)
MRSettings->SetIsCasting(true);
}
}
void FOculusXRMRModule::OnPieStarted(bool bIsSimulating)
{
// Handle the PIE world as a normal game world
UWorld* PieWorld = GEditor->GetPIEWorldContext()->World();
if (!bIsSimulating && PieWorld)
{
OnWorldCreated(PieWorld);
}
}
void FOculusXRMRModule::OnPieEnded(bool bIsSimulating)
{
UWorld* PieWorld = GEditor->GetPIEWorldContext()->World();
if (!bIsSimulating && PieWorld)
{
// Stop casting when PIE ends
MRSettings->SetIsCasting(false);
OnWorldDestroyed(PieWorld);
}
}
#endif // WITH_EDITOR
IMPLEMENT_MODULE(FOculusXRMRModule, OculusXRMR)
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,97 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "IOculusXRMRModule.h"
#include "Engine/EngineBaseTypes.h"
#define LOCTEXT_NAMESPACE "OculusXRMR"
enum class EOculusXRMR_CompositionMethod : uint8;
enum class EOculusXRMR_DepthQuality : uint8;
class UOculusXRMR_Settings;
class AOculusXRMR_CastingCameraActor;
class UOculusXRMR_State;
//-------------------------------------------------------------------------------------------------
// FOculusXRInputModule
//-------------------------------------------------------------------------------------------------
class FOculusXRMRModule : public IOculusXRMRModule
{
public:
FOculusXRMRModule();
~FOculusXRMRModule();
static inline FOculusXRMRModule& Get()
{
return FModuleManager::GetModuleChecked<FOculusXRMRModule>("OculusXRMR");
}
// IOculusXRMRModule
virtual void StartupModule() override;
virtual void ShutdownModule() override;
bool IsInitialized() { return bInitialized; }
bool IsActive();
UOculusXRMR_Settings* GetMRSettings();
UOculusXRMR_State* GetMRState();
private:
bool bInitialized;
UOculusXRMR_Settings* MRSettings;
UOculusXRMR_State* MRState;
AOculusXRMR_CastingCameraActor* MRActor;
UWorld* CurrentWorld;
FDelegateHandle WorldAddedEventBinding;
FDelegateHandle WorldDestroyedEventBinding;
FDelegateHandle WorldLoadEventBinding;
void InitMixedRealityCapture();
/** Initialize the tracked physical camera */
void SetupExternalCamera();
/** Set up the needed settings and actors for MRC in-game */
void SetupInGameCapture();
/** Destroy actors for MRC in-game */
void CloseInGameCapture();
/** Reset all the MRC settings and state to the config and default */
void ResetSettingsAndState();
/** Handle changes on specific settings */
void OnCompositionMethodChanged(EOculusXRMR_CompositionMethod OldVal, EOculusXRMR_CompositionMethod NewVal);
void OnIsCastingChanged(bool OldVal, bool NewVal);
void OnUseDynamicLightingChanged(bool OldVal, bool NewVal);
void OnDepthQualityChanged(EOculusXRMR_DepthQuality OldVal, EOculusXRMR_DepthQuality NewVal);
void OnTrackedCameraIndexChanged(int OldVal, int NewVal);
void OnWorldCreated(UWorld* NewWorld);
void OnWorldDestroyed(UWorld* NewWorld);
#if PLATFORM_ANDROID
bool bActivated;
FDelegateHandle InitialWorldAddedEventBinding;
FDelegateHandle InitialWorldLoadEventBinding;
FDelegateHandle PreWorldTickEventBinding;
void ChangeCaptureState();
void OnWorldTick(UWorld* World, ELevelTick Tick, float Delta);
void OnInitialWorldCreated(UWorld* NewWorld);
#endif
#if WITH_EDITOR
FDelegateHandle PieBeginEventBinding;
FDelegateHandle PieStartedEventBinding;
FDelegateHandle PieEndedEventBinding;
void OnPieBegin(bool bIsSimulating);
void OnPieStarted(bool bIsSimulating);
void OnPieEnded(bool bIsSimulating);
#endif
};
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,12 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "IOculusXRMRModule.h"
#include "CoreMinimal.h"
#include "OculusXRPluginWrapper.h"
#if OCULUS_MR_SUPPORTED_PLATFORMS
DEFINE_LOG_CATEGORY_STATIC(LogMR, Log, All);
#endif // OCULUS_MR_SUPPORTED_PLATFORMS

View File

@@ -0,0 +1,869 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#include "OculusXRMR_CastingCameraActor.h"
#include "OculusXRMRPrivate.h"
#include "OculusXRHMD_Settings.h"
#include "OculusXRHMD.h"
#include "OculusXRHMD_SpectatorScreenController.h"
#include "OculusXRMRModule.h"
#include "OculusXRMR_Settings.h"
#include "OculusXRMR_State.h"
#include "OculusXRMR_PlaneMeshComponent.h"
#include "OculusXRMRFunctionLibrary.h"
#include "Components/StaticMeshComponent.h"
#include "Components/SceneCaptureComponent2D.h"
#include "UObject/ConstructorHelpers.h"
#include "Engine/Engine.h"
#include "Engine/World.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/WorldSettings.h"
#include "Engine/TextureRenderTarget2D.h"
#include "Rendering/Texture2DResource.h"
#include "RenderingThread.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "VRNotificationsComponent.h"
#include "RenderUtils.h"
#include "AudioDevice.h"
#include "Materials/Material.h"
#include "RHIDefinitions.h"
#include "DataDrivenShaderPlatformInfo.h"
#define LOCTEXT_NAMESPACE "OculusXRMR_CastingCameraActor"
// Possibly add 2=Limited in a future update
static TAutoConsoleVariable<int32> CEnableExternalCompositionPostProcess(TEXT("oculus.mr.ExternalCompositionPostProcess"), 0, TEXT("Enable MR external composition post process: 0=Off, 1=On"));
static TAutoConsoleVariable<int32> COverrideMixedRealityParametersVar(TEXT("oculus.mr.OverrideParameters"), 0, TEXT("Use the Mixed Reality console variables"));
namespace
{
bool GetCameraTrackedObjectPoseInTrackingSpace(OculusXRHMD::FOculusXRHMD* OculusXRHMD, const FOculusXRTrackedCamera& TrackedCamera, OculusXRHMD::FPose& CameraTrackedObjectPose)
{
using namespace OculusXRHMD;
CameraTrackedObjectPose = FPose(FQuat::Identity, FVector::ZeroVector);
if (TrackedCamera.AttachedTrackedDevice != EOculusXRTrackedDeviceType::None)
{
ovrpResult result = ovrpSuccess;
ovrpPoseStatef cameraPoseState;
ovrpNode deviceNode = ToOvrpNode(TrackedCamera.AttachedTrackedDevice);
ovrpBool nodePresent = ovrpBool_False;
result = FOculusXRHMDModule::GetPluginWrapper().GetNodePresent2(deviceNode, &nodePresent);
if (OVRP_FAILURE(result))
{
UE_LOG(LogMR, Warning, TEXT("Unable to check if AttachedTrackedDevice is present"));
return false;
}
if (!nodePresent)
{
UE_LOG(LogMR, Warning, TEXT("AttachedTrackedDevice is not present"));
return false;
}
OculusXRHMD::FGameFrame* CurrentFrame;
if (IsInGameThread())
{
CurrentFrame = OculusXRHMD->GetNextFrameToRender();
}
else
{
CurrentFrame = OculusXRHMD->GetFrame_RenderThread();
}
result = CurrentFrame ? FOculusXRHMDModule::GetPluginWrapper().GetNodePoseState3(ovrpStep_Render, CurrentFrame->FrameNumber, deviceNode, &cameraPoseState) : ovrpFailure;
if (OVRP_FAILURE(result))
{
UE_LOG(LogMR, Warning, TEXT("Unable to retrieve AttachedTrackedDevice pose state"));
return false;
}
OculusXRHMD->ConvertPose(cameraPoseState.Pose, CameraTrackedObjectPose);
}
return true;
}
} // namespace
//////////////////////////////////////////////////////////////////////////
// ACastingCameraActor
AOculusXRMR_CastingCameraActor::AOculusXRMR_CastingCameraActor(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, TrackedCameraCalibrationRequired(false)
, HasTrackedCameraCalibrationCalibrated(false)
, RefreshBoundaryMeshCounter(3)
, ForegroundLayerBackgroundColor(FColor::Green)
, ForegroundMaxDistance(300.0f)
{
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bTickEvenWhenPaused = true;
VRNotificationComponent = CreateDefaultSubobject<UVRNotificationsComponent>(TEXT("VRNotificationComponent"));
#if PLATFORM_WINDOWS
PlaneMeshComponent = CreateDefaultSubobject<UOculusXRMR_PlaneMeshComponent>(TEXT("PlaneMeshComponent"));
PlaneMeshComponent->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
PlaneMeshComponent->ResetRelativeTransform();
PlaneMeshComponent->SetVisibility(false);
#endif
OpaqueColoredMaterial = Cast<UMaterial>(StaticLoadObject(UMaterial::StaticClass(), nullptr, TEXT("/OculusXR/Materials/OculusMR_OpaqueColoredMaterial")));
if (!OpaqueColoredMaterial)
{
UE_LOG(LogMR, Warning, TEXT("Invalid OpaqueColoredMaterial"));
}
// Structure to hold one-time initialization
struct FConstructorStatics
{
ConstructorHelpers::FObjectFinder<UTexture2D> WhiteSquareTexture;
FConstructorStatics()
: WhiteSquareTexture(TEXT("/Engine/EngineResources/WhiteSquareTexture"))
{
}
};
static FConstructorStatics ConstructorStatics;
DefaultTexture_White = ConstructorStatics.WhiteSquareTexture.Object;
check(DefaultTexture_White);
ForegroundCaptureActor = nullptr;
// Set the render targets for background and foreground to copies of the default texture
#if PLATFORM_WINDOWS
BackgroundRenderTargets.SetNum(1);
ForegroundRenderTargets.SetNum(1);
BackgroundRenderTargets[0] = NewObject<UTextureRenderTarget2D>();
BackgroundRenderTargets[0]->RenderTargetFormat = RTF_RGBA8_SRGB;
ForegroundRenderTargets[0] = NewObject<UTextureRenderTarget2D>();
ForegroundRenderTargets[0]->RenderTargetFormat = RTF_RGBA8_SRGB;
#elif PLATFORM_ANDROID
BackgroundRenderTargets.SetNum(NumRTs);
ForegroundRenderTargets.SetNum(NumRTs);
AudioBuffers.SetNum(NumRTs);
AudioTimes.SetNum(NumRTs);
PoseTimes.SetNum(NumRTs);
for (unsigned int i = 0; i < NumRTs; ++i)
{
BackgroundRenderTargets[i] = NewObject<UTextureRenderTarget2D>();
BackgroundRenderTargets[i]->RenderTargetFormat = RTF_RGBA8_SRGB;
ForegroundRenderTargets[i] = NewObject<UTextureRenderTarget2D>();
ForegroundRenderTargets[i]->RenderTargetFormat = RTF_RGBA8_SRGB;
AudioTimes[i] = 0.0;
PoseTimes[i] = 0.0;
}
SyncId = -1;
RenderedRTs = 0;
CaptureIndex = 0;
#endif
}
void AOculusXRMR_CastingCameraActor::BeginDestroy()
{
CloseTrackedCamera();
Super::BeginDestroy();
}
bool AOculusXRMR_CastingCameraActor::RefreshExternalCamera()
{
using namespace OculusXRHMD;
if (MRState->TrackedCamera.Index >= 0)
{
int cameraCount;
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetExternalCameraCount(&cameraCount)))
{
cameraCount = 0;
}
if (MRState->TrackedCamera.Index >= cameraCount)
{
UE_LOG(LogMR, Error, TEXT("Invalid TrackedCamera Index"));
return false;
}
FOculusXRHMD* OculusXRHMD = GEngine->XRSystem.IsValid() ? (FOculusXRHMD*)(GEngine->XRSystem->GetHMDDevice()) : nullptr;
if (!OculusXRHMD)
{
UE_LOG(LogMR, Error, TEXT("Unable to retrieve OculusXRHMD"));
return false;
}
ovrpResult result = ovrpSuccess;
ovrpCameraExtrinsics cameraExtrinsics;
result = FOculusXRHMDModule::GetPluginWrapper().GetExternalCameraExtrinsics(MRState->TrackedCamera.Index, &cameraExtrinsics);
if (OVRP_FAILURE(result))
{
UE_LOG(LogMR, Error, TEXT("FOculusXRHMDModule::GetPluginWrapper().GetExternalCameraExtrinsics failed"));
return false;
}
MRState->TrackedCamera.AttachedTrackedDevice = OculusXRHMD::ToEOculusXRTrackedDeviceType(cameraExtrinsics.AttachedToNode);
OculusXRHMD::FPose Pose;
OculusXRHMD->ConvertPose(cameraExtrinsics.RelativePose, Pose);
MRState->TrackedCamera.CalibratedRotation = Pose.Orientation.Rotator();
MRState->TrackedCamera.CalibratedOffset = Pose.Position;
MRState->TrackedCamera.UpdateTime = cameraExtrinsics.LastChangedTimeSeconds;
}
return true;
}
void AOculusXRMR_CastingCameraActor::BeginPlay()
{
Super::BeginPlay();
SetupTrackedCamera();
RequestTrackedCameraCalibration();
SetupMRCScreen();
FScriptDelegate Delegate;
Delegate.BindUFunction(this, FName(TEXT("OnHMDRecentered")));
VRNotificationComponent->HMDRecenteredDelegate.Add(Delegate);
#if PLATFORM_ANDROID
FAudioDeviceHandle AudioDevice = FAudioDevice::GetMainAudioDevice();
if (AudioDevice.GetAudioDevice())
{
AudioDevice->StartRecording(nullptr, 0.1);
}
#endif
}
void AOculusXRMR_CastingCameraActor::EndPlay(EEndPlayReason::Type Reason)
{
#if PLATFORM_ANDROID
FAudioDeviceHandle AudioDevice = FAudioDevice::GetMainAudioDevice();
if (AudioDevice.GetAudioDevice())
{
float NumChannels = 2;
float SampleRate = AudioDevice->GetSampleRate();
AudioDevice->StopRecording(nullptr, NumChannels, SampleRate);
}
#endif
VRNotificationComponent->HMDRecenteredDelegate.Remove(this, FName(TEXT("OnHMDRecentered")));
MRState->TrackingReferenceComponent = nullptr;
CloseMRCScreen();
CloseTrackedCamera();
Super::EndPlay(Reason);
}
void AOculusXRMR_CastingCameraActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (MRState->BindToTrackedCameraIndexRequested)
{
Execute_BindToTrackedCameraIndexIfAvailable();
}
if (!RefreshExternalCamera())
{
CloseTrackedCamera();
return;
}
// Reset capturing components if the composition method changes
if (MRState->ChangeCameraStateRequested)
{
CloseTrackedCamera();
CloseMRCScreen();
SetupTrackedCamera();
SetupMRCScreen();
}
#if PLATFORM_WINDOWS
if (MRSettings->GetCompositionMethod() == EOculusXRMR_CompositionMethod::ExternalComposition)
#endif
{
if (ForegroundLayerBackgroundColor != MRSettings->BackdropColor)
{
ForegroundLayerBackgroundColor = MRSettings->BackdropColor;
SetBackdropMaterialColor();
}
// Enable external composition post process based on setting
bool bPostProcess = MRSettings->ExternalCompositionPostProcessEffects != EOculusXRMR_PostProcessEffects::PPE_Off;
if (COverrideMixedRealityParametersVar.GetValueOnAnyThread() > 0)
{
bPostProcess = CEnableExternalCompositionPostProcess.GetValueOnAnyThread() > 0;
}
GetCaptureComponent2D()->ShowFlags.PostProcessing = bPostProcess;
if (ForegroundCaptureActor)
{
ForegroundCaptureActor->GetCaptureComponent2D()->ShowFlags.PostProcessing = bPostProcess;
}
}
if (TrackedCameraCalibrationRequired)
{
CalibrateTrackedCameraPose();
}
UpdateTrackedCameraPosition();
#if PLATFORM_WINDOWS
RepositionPlaneMesh();
#endif
UpdateRenderTargetSize();
#if PLATFORM_ANDROID
OculusXRHMD::FOculusXRHMD* OculusXRHMD = GEngine->XRSystem.IsValid() ? (OculusXRHMD::FOculusXRHMD*)(GEngine->XRSystem->GetHMDDevice()) : nullptr;
if (OculusXRHMD)
{
ovrpPosef OvrpPose, OvrpHeadPose, OvrpLeftHandPose, OvrpRightHandPose;
FOculusXRHMDModule::GetPluginWrapper().GetTrackingTransformRelativePose(&OvrpPose, ovrpTrackingOrigin_Stage);
OculusXRHMD::FPose StageToLocalPose;
OculusXRHMD->ConvertPose(OvrpPose, StageToLocalPose);
OculusXRHMD::FPose LocalToStagePose = StageToLocalPose.Inverse();
OculusXRHMD::FPose HeadPose;
OculusXRHMD->GetCurrentPose(OculusXRHMD::ToExternalDeviceId(ovrpNode_Head), HeadPose.Orientation, HeadPose.Position);
HeadPose = LocalToStagePose * HeadPose;
OculusXRHMD->ConvertPose(HeadPose, OvrpHeadPose);
OculusXRHMD::FPose LeftHandPose;
OculusXRHMD->GetCurrentPose(OculusXRHMD::ToExternalDeviceId(ovrpNode_HandLeft), HeadPose.Orientation, HeadPose.Position);
LeftHandPose = LocalToStagePose * LeftHandPose;
OculusXRHMD->ConvertPose(LeftHandPose, OvrpLeftHandPose);
OculusXRHMD::FPose RightHandPose;
OculusXRHMD->GetCurrentPose(OculusXRHMD::ToExternalDeviceId(ovrpNode_HandRight), HeadPose.Orientation, HeadPose.Position);
RightHandPose = LocalToStagePose * RightHandPose;
OculusXRHMD->ConvertPose(RightHandPose, OvrpRightHandPose);
FOculusXRHMDModule::GetPluginWrapper().Media_SetHeadsetControllerPose(OvrpHeadPose, OvrpLeftHandPose, OvrpRightHandPose);
}
// Alternate foreground and background captures by nulling the capture component texture target
if (GetCaptureComponent2D()->IsVisible())
{
GetCaptureComponent2D()->SetVisibility(false);
// Encode a texture the frame before we render to it again to ensure completed render at the cost of latency
unsigned int EncodeIndex = (CaptureIndex + 1) % NumRTs;
// Skip encoding for the first few frames before they have completed rendering
if (RenderedRTs > EncodeIndex)
{
FOculusXRHMDModule::GetPluginWrapper().Media_SyncMrcFrame(SyncId);
int NumChannels = 2;
double AudioTime = AudioTimes[EncodeIndex];
void* BackgroundTexture;
void* ForegroundTexture;
if (IsVulkanPlatform(GMaxRHIShaderPlatform))
{
ExecuteOnRenderThread([this, EncodeIndex, &BackgroundTexture, &ForegroundTexture]() {
ExecuteOnRHIThread([this, EncodeIndex, &BackgroundTexture, &ForegroundTexture]() {
// The Vulkan RHI's implementation of GetNativeResource is different and returns the VkImage cast
// as a void* instead of a pointer to the VkImage, so we need this workaround
BackgroundTexture = (void*)BackgroundRenderTargets[EncodeIndex]->GetResource()->TextureRHI->GetNativeResource();
ForegroundTexture = (void*)ForegroundRenderTargets[EncodeIndex]->GetResource()->TextureRHI->GetNativeResource();
});
});
}
else
{
ExecuteOnRenderThread([this, EncodeIndex, &BackgroundTexture, &ForegroundTexture]() {
ExecuteOnRHIThread([this, EncodeIndex, &BackgroundTexture, &ForegroundTexture]() {
BackgroundTexture = *((void**)BackgroundRenderTargets[EncodeIndex]->GetResource()->TextureRHI->GetNativeResource());
ForegroundTexture = *((void**)ForegroundRenderTargets[EncodeIndex]->GetResource()->TextureRHI->GetNativeResource());
});
});
}
FOculusXRHMDModule::GetPluginWrapper().Media_EncodeMrcFrameDualTexturesWithPoseTime(BackgroundTexture, ForegroundTexture, AudioBuffers[EncodeIndex].GetData(), AudioBuffers[EncodeIndex].Num() * sizeof(float), NumChannels, AudioTime, PoseTimes[CaptureIndex], &SyncId);
}
ForegroundCaptureActor->GetCaptureComponent2D()->SetVisibility(true);
}
else if (ForegroundCaptureActor && ForegroundCaptureActor->GetCaptureComponent2D()->IsVisible())
{
ForegroundCaptureActor->GetCaptureComponent2D()->SetVisibility(false);
// Increment scene captures to next texture
CaptureIndex = (CaptureIndex + 1) % NumRTs;
GetCaptureComponent2D()->TextureTarget = BackgroundRenderTargets[CaptureIndex];
ForegroundCaptureActor->GetCaptureComponent2D()->TextureTarget = ForegroundRenderTargets[CaptureIndex];
GetCaptureComponent2D()->SetVisibility(true);
FAudioDeviceHandle AudioDevice = FAudioDevice::GetMainAudioDevice();
if (AudioDevice.GetAudioDevice())
{
float NumChannels, SampleRate;
NumChannels = 2;
SampleRate = AudioDevice->GetSampleRate();
AudioBuffers[CaptureIndex] = AudioDevice->StopRecording(nullptr, NumChannels, SampleRate);
AudioTimes[CaptureIndex] = AudioDevice->GetAudioTime();
// UE_LOG(LogMR, Error, TEXT("SampleRate: %f, NumChannels: %f, Time: %f, Buffer Length: %d, Buffer: %p"), SampleRate, NumChannels, AudioDevice->GetAudioTime(), AudioBuffers[EncodeIndex].Num(), AudioBuffers[EncodeIndex].GetData());
AudioDevice->StartRecording(nullptr, 0.1);
}
// PoseTimes[CaptureIndex] = MRState->TrackedCamera.UpdateTime;
// Increment this counter for the initial cycle through "swapchain"
if (RenderedRTs < NumRTs)
{
RenderedRTs++;
}
}
#endif
}
void AOculusXRMR_CastingCameraActor::Execute_BindToTrackedCameraIndexIfAvailable()
{
if (!MRState->BindToTrackedCameraIndexRequested)
{
return;
}
FOculusXRTrackedCamera TempTrackedCamera;
if (MRSettings->GetBindToTrackedCameraIndex() >= 0)
{
TArray<FOculusXRTrackedCamera> TrackedCameras;
UOculusXRMRFunctionLibrary::GetAllTrackedCamera(TrackedCameras);
int i;
for (i = 0; i < TrackedCameras.Num(); ++i)
{
if (TrackedCameras[i].Index == MRSettings->GetBindToTrackedCameraIndex())
{
TempTrackedCamera = TrackedCameras[i];
break;
}
}
if (i == TrackedCameras.Num())
{
UE_LOG(LogMR, Warning, TEXT("Unable to find TrackedCamera at index %d, use TempTrackedCamera"), MRSettings->GetBindToTrackedCameraIndex());
}
}
else
{
UE_LOG(LogMR, Warning, TEXT("BindToTrackedCameraIndex == %d, use TempTrackedCamera"), MRSettings->GetBindToTrackedCameraIndex());
}
MRState->TrackedCamera = TempTrackedCamera;
if (MRState->TrackedCamera.Index < 0)
{
SetTrackedCameraUserPoseWithCameraTransform();
}
MRState->BindToTrackedCameraIndexRequested = false;
}
void AOculusXRMR_CastingCameraActor::RequestTrackedCameraCalibration()
{
TrackedCameraCalibrationRequired = true;
}
void AOculusXRMR_CastingCameraActor::CalibrateTrackedCameraPose()
{
SetTrackedCameraInitialPoseWithPlayerTransform();
HasTrackedCameraCalibrationCalibrated = true;
TrackedCameraCalibrationRequired = false;
}
void AOculusXRMR_CastingCameraActor::SetTrackedCameraInitialPoseWithPlayerTransform()
{
using namespace OculusXRHMD;
FOculusXRHMD* OculusXRHMD = GEngine->XRSystem.IsValid() ? (FOculusXRHMD*)(GEngine->XRSystem->GetHMDDevice()) : nullptr;
if (!OculusXRHMD)
{
UE_LOG(LogMR, Warning, TEXT("Unable to retrieve OculusXRHMD"));
return;
}
FPose CameraTrackedObjectPose;
if (!GetCameraTrackedObjectPoseInTrackingSpace(OculusXRHMD, MRState->TrackedCamera, CameraTrackedObjectPose))
{
return;
}
FPose CameraPose = CameraTrackedObjectPose * FPose(MRState->TrackedCamera.CalibratedRotation.Quaternion(), MRState->TrackedCamera.CalibratedOffset);
CameraPose = CameraPose * FPose(MRState->TrackedCamera.UserRotation.Quaternion(), MRState->TrackedCamera.UserOffset);
FQuat TROrientation;
FVector TRLocation;
FRotator TRRotation;
if (!UOculusXRMRFunctionLibrary::GetTrackingReferenceLocationAndRotationInWorldSpace(MRState->TrackingReferenceComponent, TRLocation, TRRotation))
{
UE_LOG(LogMR, Warning, TEXT("Could not get player position"));
return;
}
TROrientation = TRRotation.Quaternion();
FPose FinalPose = FPose(TROrientation, TRLocation) * CameraPose;
InitialCameraAbsoluteOrientation = FinalPose.Orientation;
InitialCameraAbsolutePosition = FinalPose.Position;
InitialCameraRelativeOrientation = CameraPose.Orientation;
InitialCameraRelativePosition = CameraPose.Position;
GetCaptureComponent2D()->FOVAngle = MRState->TrackedCamera.FieldOfView;
if (ForegroundCaptureActor)
{
ForegroundCaptureActor->GetCaptureComponent2D()->FOVAngle = MRState->TrackedCamera.FieldOfView;
}
}
void AOculusXRMR_CastingCameraActor::SetTrackedCameraUserPoseWithCameraTransform()
{
using namespace OculusXRHMD;
FOculusXRHMD* OculusXRHMD = GEngine->XRSystem.IsValid() ? (FOculusXRHMD*)(GEngine->XRSystem->GetHMDDevice()) : nullptr;
if (!OculusXRHMD)
{
UE_LOG(LogMR, Warning, TEXT("Unable to retrieve OculusXRHMD"));
return;
}
FPose CameraTrackedObjectPose;
if (!GetCameraTrackedObjectPoseInTrackingSpace(OculusXRHMD, MRState->TrackedCamera, CameraTrackedObjectPose))
{
return;
}
FPose CameraPose = CameraTrackedObjectPose * FPose(MRState->TrackedCamera.CalibratedRotation.Quaternion(), MRState->TrackedCamera.CalibratedOffset);
FQuat TROrientation;
FVector TRLocation;
FRotator TRRotation;
if (!UOculusXRMRFunctionLibrary::GetTrackingReferenceLocationAndRotationInWorldSpace(MRState->TrackingReferenceComponent, TRLocation, TRRotation))
{
UE_LOG(LogMR, Warning, TEXT("Could not get player position"));
return;
}
TROrientation = TRRotation.Quaternion();
FPose PlayerPose(TROrientation, TRLocation);
FPose CurrentCameraPose = PlayerPose * CameraPose;
FPose ExpectedCameraPose(GetCaptureComponent2D()->GetComponentRotation().Quaternion(), GetCaptureComponent2D()->GetComponentLocation());
FPose UserPose = CurrentCameraPose.Inverse() * ExpectedCameraPose;
MRState->TrackedCamera.UserRotation = UserPose.Orientation.Rotator();
MRState->TrackedCamera.UserOffset = UserPose.Position;
}
void AOculusXRMR_CastingCameraActor::UpdateTrackedCameraPosition()
{
check(HasTrackedCameraCalibrationCalibrated);
using namespace OculusXRHMD;
FOculusXRHMD* OculusXRHMD = GEngine->XRSystem.IsValid() ? (FOculusXRHMD*)(GEngine->XRSystem->GetHMDDevice()) : nullptr;
if (!OculusXRHMD)
{
UE_LOG(LogMR, Warning, TEXT("Unable to retrieve OculusXRHMD"));
return;
}
FPose CameraTrackedObjectPose;
if (!GetCameraTrackedObjectPoseInTrackingSpace(OculusXRHMD, MRState->TrackedCamera, CameraTrackedObjectPose))
{
return;
}
FPose CameraTrackingSpacePose = FPose(MRState->TrackedCamera.CalibratedRotation.Quaternion(), MRState->TrackedCamera.CalibratedOffset);
#if PLATFORM_ANDROID
ovrpPosef OvrpPose;
FOculusXRHMDModule::GetPluginWrapper().GetTrackingTransformRelativePose(&OvrpPose, ovrpTrackingOrigin_Stage);
FPose StageToLocalPose;
OculusXRHMD->ConvertPose(OvrpPose, StageToLocalPose);
CameraTrackingSpacePose = StageToLocalPose * CameraTrackingSpacePose;
#endif
FPose CameraPose = CameraTrackedObjectPose * CameraTrackingSpacePose;
CameraPose = CameraPose * FPose(MRState->TrackedCamera.UserRotation.Quaternion(), MRState->TrackedCamera.UserOffset);
CameraPose.Position = CameraPose.Position * MRState->ScalingFactor;
float Distance = 0.0f;
if (MRSettings->ClippingReference == EOculusXRMR_ClippingReference::CR_TrackingReference)
{
Distance = -FVector::DotProduct(CameraPose.Orientation.GetForwardVector().GetSafeNormal2D(), CameraPose.Position);
}
else if (MRSettings->ClippingReference == EOculusXRMR_ClippingReference::CR_Head)
{
FQuat HeadOrientation;
FVector HeadPosition;
OculusXRHMD->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, HeadOrientation, HeadPosition);
FVector HeadToCamera = HeadPosition - CameraPose.Position;
Distance = FVector::DotProduct(CameraPose.Orientation.GetForwardVector().GetSafeNormal2D(), HeadToCamera);
}
else
{
checkNoEntry();
}
ForegroundMaxDistance = FMath::Max(Distance, GMinClipZ);
if (ForegroundCaptureActor)
{
ForegroundCaptureActor->GetCaptureComponent2D()->MaxViewDistanceOverride = ForegroundMaxDistance;
}
FPose FinalPose;
FQuat TROrientation;
FVector TRLocation;
FRotator TRRotation;
if (!UOculusXRMRFunctionLibrary::GetTrackingReferenceLocationAndRotationInWorldSpace(MRState->TrackingReferenceComponent, TRLocation, TRRotation))
{
UE_LOG(LogMR, Warning, TEXT("Could not get player position"));
return;
}
TROrientation = TRRotation.Quaternion();
FinalPose = FPose(TROrientation, TRLocation) * CameraPose;
FTransform FinalTransform(FinalPose.Orientation, FinalPose.Position);
RootComponent->SetWorldTransform(FinalTransform);
GetCaptureComponent2D()->FOVAngle = MRState->TrackedCamera.FieldOfView;
if (ForegroundCaptureActor)
{
ForegroundCaptureActor->GetCaptureComponent2D()->FOVAngle = MRState->TrackedCamera.FieldOfView;
}
}
void AOculusXRMR_CastingCameraActor::InitializeStates(UOculusXRMR_Settings* MRSettingsIn, UOculusXRMR_State* MRStateIn)
{
MRSettings = MRSettingsIn;
MRState = MRStateIn;
}
void AOculusXRMR_CastingCameraActor::SetupTrackedCamera()
{
if (!RefreshExternalCamera())
{
return;
}
RequestTrackedCameraCalibration();
// Unset this flag before we can return
MRState->ChangeCameraStateRequested = false;
#if PLATFORM_WINDOWS
if (MRSettings->GetCompositionMethod() == EOculusXRMR_CompositionMethod::ExternalComposition)
{
SetupBackdropMaterialInstance();
}
RepositionPlaneMesh();
#endif
}
void AOculusXRMR_CastingCameraActor::SetBackdropMaterialColor()
{
if (BackdropMaterialInstance)
{
BackdropMaterialInstance->SetVectorParameterValue(FName(TEXT("Color")), GetForegroundLayerBackgroundColor());
}
}
void AOculusXRMR_CastingCameraActor::SetupBackdropMaterialInstance()
{
if (!BackdropMaterialInstance && OpaqueColoredMaterial)
{
BackdropMaterialInstance = UMaterialInstanceDynamic::Create(OpaqueColoredMaterial, this);
BackdropMaterialInstance->SetScalarParameterValue(FName("Opacity"), 0.0f);
}
PlaneMeshComponent->SetMaterial(0, BackdropMaterialInstance);
SetBackdropMaterialColor();
}
void AOculusXRMR_CastingCameraActor::RepositionPlaneMesh()
{
FVector PlaneCenter = FVector::ForwardVector * ForegroundMaxDistance;
FVector PlaneUp = FVector::UpVector;
FVector PlaneNormal = -FVector::ForwardVector;
int ViewWidth = MRSettings->bUseTrackedCameraResolution ? MRState->TrackedCamera.SizeX : MRSettings->WidthPerView;
int ViewHeight = MRSettings->bUseTrackedCameraResolution ? MRState->TrackedCamera.SizeY : MRSettings->HeightPerView;
float Width = ForegroundMaxDistance * FMath::Tan(FMath::DegreesToRadians(GetCaptureComponent2D()->FOVAngle) * 0.5f) * 2.0f;
float Height = Width * ViewHeight / ViewWidth;
FVector2D PlaneSize = FVector2D(Width, Height);
PlaneMeshComponent->Place(PlaneCenter, PlaneUp, PlaneNormal, PlaneSize);
PlaneMeshComponent->ResetRelativeTransform();
PlaneMeshComponent->SetVisibility(true);
}
void AOculusXRMR_CastingCameraActor::OnHMDRecentered()
{
#if PLATFORM_WINDOWS
RefreshBoundaryMesh();
#endif
RequestTrackedCameraCalibration();
}
void AOculusXRMR_CastingCameraActor::RefreshBoundaryMesh()
{
RefreshBoundaryMeshCounter = 3;
}
void BuildProjectionMatrix(float YMultiplier, float FOV, float FarClipPlane, FMatrix& ProjectionMatrix)
{
if (FarClipPlane < GNearClippingPlane)
{
FarClipPlane = GNearClippingPlane;
}
if ((int32)ERHIZBuffer::IsInverted)
{
ProjectionMatrix = FReversedZPerspectiveMatrix(
FOV,
FOV,
1.0f,
YMultiplier,
GNearClippingPlane,
FarClipPlane);
}
else
{
ProjectionMatrix = FPerspectiveMatrix(
FOV,
FOV,
1.0f,
YMultiplier,
GNearClippingPlane,
FarClipPlane);
}
}
void AOculusXRMR_CastingCameraActor::UpdateRenderTargetSize()
{
int ViewWidth = MRSettings->bUseTrackedCameraResolution ? MRState->TrackedCamera.SizeX : MRSettings->WidthPerView;
int ViewHeight = MRSettings->bUseTrackedCameraResolution ? MRState->TrackedCamera.SizeY : MRSettings->HeightPerView;
#if PLATFORM_WINDOWS
BackgroundRenderTargets[0]->ResizeTarget(ViewWidth, ViewHeight);
if (ForegroundRenderTargets[0])
{
ForegroundRenderTargets[0]->ResizeTarget(ViewWidth, ViewHeight);
}
#endif
#if PLATFORM_ANDROID
FIntPoint CameraTargetSize = FIntPoint(ViewWidth, ViewHeight);
float FOV = GetCaptureComponent2D()->FOVAngle * (float)PI / 360.0f;
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().Media_GetMrcFrameSize(&ViewWidth, &ViewHeight)))
{
// Frame size is doublewide, so divide by 2
ViewWidth /= 2;
for (unsigned int i = 0; i < NumRTs; ++i)
{
BackgroundRenderTargets[i]->ResizeTarget(ViewWidth, ViewHeight);
if (ForegroundRenderTargets[i])
{
ForegroundRenderTargets[i]->ResizeTarget(ViewWidth, ViewHeight);
}
}
// Use custom projection matrix for far clip plane and to use camera aspect ratio instead of rendertarget aspect ratio
float YMultiplier = (float)CameraTargetSize.X / (float)CameraTargetSize.Y;
GetCaptureComponent2D()->bUseCustomProjectionMatrix = true;
BuildProjectionMatrix(YMultiplier, FOV, GNearClippingPlane, GetCaptureComponent2D()->CustomProjectionMatrix);
if (ForegroundCaptureActor)
{
ForegroundCaptureActor->GetCaptureComponent2D()->bUseCustomProjectionMatrix = true;
BuildProjectionMatrix(YMultiplier, FOV, ForegroundMaxDistance, ForegroundCaptureActor->GetCaptureComponent2D()->CustomProjectionMatrix);
}
}
#endif
}
void AOculusXRMR_CastingCameraActor::SetupMRCScreen()
{
#if PLATFORM_WINDOWS
OculusXRHMD::FSpectatorScreenController* SpecScreen = nullptr;
IHeadMountedDisplay* HMD = GEngine->XRSystem.IsValid() ? GEngine->XRSystem->GetHMDDevice() : nullptr;
if (HMD)
{
SpecScreen = (OculusXRHMD::FSpectatorScreenController*)HMD->GetSpectatorScreenController();
}
if (SpecScreen)
{
#endif
UpdateRenderTargetSize();
// LDR for gamma correction and post process
GetCaptureComponent2D()->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR;
// Render scene capture 2D output to spectator screen
GetCaptureComponent2D()->TextureTarget = BackgroundRenderTargets[0];
#if PLATFORM_WINDOWS
if (MRSettings->GetCompositionMethod() == EOculusXRMR_CompositionMethod::ExternalComposition)
#endif
{
ForegroundCaptureActor = GetWorld()->SpawnActor<ASceneCapture2D>();
// LDR for gamma correction and post process
ForegroundCaptureActor->GetCaptureComponent2D()->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR;
#if PLATFORM_ANDROID
// Start with foreground capture actor off on android
ForegroundCaptureActor->GetCaptureComponent2D()->SetVisibility(false);
#endif
// Don't render anything past the foreground for performance
ForegroundCaptureActor->GetCaptureComponent2D()->MaxViewDistanceOverride = ForegroundMaxDistance;
ForegroundCaptureActor->GetCaptureComponent2D()->TextureTarget = ForegroundRenderTargets[0];
#if PLATFORM_WINDOWS
// Render use split foreground/background rendering to spectator screen
SpecScreen->SetMRForeground(ForegroundRenderTargets[0]);
SpecScreen->SetMRBackground(BackgroundRenderTargets[0]);
SpecScreen->SetMRSpectatorScreenMode(OculusXRHMD::EMRSpectatorScreenMode::ExternalComposition);
// Set the plane mesh to only render to foreground target
PlaneMeshComponent->SetPlaneRenderTarget(ForegroundRenderTargets[0]);
#endif
// Set foreground capture to match background capture
ForegroundCaptureActor->AttachToActor(this, FAttachmentTransformRules(EAttachmentRule::SnapToTarget, true));
}
#if PLATFORM_WINDOWS
}
else
{
UE_LOG(LogMR, Error, TEXT("Cannot find spectator screen"));
}
#endif
}
void AOculusXRMR_CastingCameraActor::CloseMRCScreen()
{
#if PLATFORM_WINDOWS
OculusXRHMD::FSpectatorScreenController* SpecScreen = nullptr;
IHeadMountedDisplay* HMD = GEngine->XRSystem.IsValid() ? GEngine->XRSystem->GetHMDDevice() : nullptr;
if (HMD)
{
SpecScreen = (OculusXRHMD::FSpectatorScreenController*)HMD->GetSpectatorScreenController();
}
// Restore original spectator screen mode
if (SpecScreen)
{
SpecScreen->SetMRSpectatorScreenMode(OculusXRHMD::EMRSpectatorScreenMode::Default);
SpecScreen->SetMRForeground(nullptr);
SpecScreen->SetMRBackground(nullptr);
}
#endif
if (ForegroundCaptureActor)
{
ForegroundCaptureActor->Destroy();
ForegroundCaptureActor = nullptr;
}
}
void AOculusXRMR_CastingCameraActor::CloseTrackedCamera()
{
if (PlaneMeshComponent)
{
PlaneMeshComponent->SetVisibility(false);
}
}
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,131 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "UObject/ObjectMacros.h"
#include "Engine/SceneCapture2D.h"
#include "AudioResampler.h"
#include "AudioDefines.h"
#include "OculusXRPluginWrapper.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "AudioMixer.h"
#include "OculusXRMR_CastingCameraActor.generated.h"
#if PLATFORM_ANDROID
#define MRC_SWAPCHAIN_LENGTH 3
#endif
class UOculusXRMR_PlaneMeshComponent;
class UMaterial;
class AOculusXRMR_BoundaryActor;
class UTextureRenderTarget2D;
class UOculusXRMR_Settings;
class UOculusXRMR_State;
/**
* The camera actor in the level that tracks the binded physical camera in game
*/
UCLASS(ClassGroup = OculusXRMR, NotPlaceable, NotBlueprintable)
class AOculusXRMR_CastingCameraActor : public ASceneCapture2D
{
GENERATED_BODY()
public:
AOculusXRMR_CastingCameraActor(const FObjectInitializer& ObjectInitializer);
/** Initialize the MRC settings and states */
void InitializeStates(UOculusXRMR_Settings* MRSettingsIn, UOculusXRMR_State* MRStateIn);
virtual void BeginPlay() override;
virtual void EndPlay(EEndPlayReason::Type Reason) override;
virtual void Tick(float DeltaTime) override;
virtual void BeginDestroy() override;
UPROPERTY()
class UVRNotificationsComponent* VRNotificationComponent;
UPROPERTY()
UOculusXRMR_PlaneMeshComponent* PlaneMeshComponent;
UPROPERTY()
UMaterial* OpaqueColoredMaterial;
UPROPERTY()
UMaterialInstanceDynamic* BackdropMaterialInstance;
UPROPERTY()
class UTexture2D* DefaultTexture_White;
bool TrackedCameraCalibrationRequired;
bool HasTrackedCameraCalibrationCalibrated;
FQuat InitialCameraAbsoluteOrientation;
FVector InitialCameraAbsolutePosition;
FQuat InitialCameraRelativeOrientation;
FVector InitialCameraRelativePosition;
int32 RefreshBoundaryMeshCounter;
private:
/** Move the casting camera to follow the tracking reference (i.e. player) */
void RequestTrackedCameraCalibration();
bool RefreshExternalCamera();
void CalibrateTrackedCameraPose();
void SetTrackedCameraUserPoseWithCameraTransform();
void SetTrackedCameraInitialPoseWithPlayerTransform();
void UpdateTrackedCameraPosition();
/** Initialize the tracked physical camera */
void SetupTrackedCamera();
/** Close the tracked physical camera */
void CloseTrackedCamera();
void OnHMDRecentered();
const FColor& GetForegroundLayerBackgroundColor() const { return ForegroundLayerBackgroundColor; }
void SetBackdropMaterialColor();
void SetupBackdropMaterialInstance();
void RepositionPlaneMesh();
void RefreshBoundaryMesh();
void UpdateRenderTargetSize();
void SetupMRCScreen();
void CloseMRCScreen();
void Execute_BindToTrackedCameraIndexIfAvailable();
FColor ForegroundLayerBackgroundColor;
float ForegroundMaxDistance;
UPROPERTY()
TArray<UTextureRenderTarget2D*> BackgroundRenderTargets;
UPROPERTY()
ASceneCapture2D* ForegroundCaptureActor;
UPROPERTY()
TArray<UTextureRenderTarget2D*> ForegroundRenderTargets;
UPROPERTY()
TArray<double> PoseTimes;
UPROPERTY()
UOculusXRMR_Settings* MRSettings;
UPROPERTY()
UOculusXRMR_State* MRState;
#if PLATFORM_ANDROID
TArray<Audio::AlignedFloatBuffer> AudioBuffers;
TArray<double> AudioTimes;
int SyncId;
const unsigned int NumRTs = MRC_SWAPCHAIN_LENGTH;
unsigned int RenderedRTs;
unsigned int CaptureIndex;
#endif
};

View File

@@ -0,0 +1,270 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#include "OculusXRMR_PlaneMeshComponent.h"
#include "RenderingThread.h"
#include "RenderResource.h"
#include "PrimitiveViewRelevance.h"
#include "PrimitiveSceneProxy.h"
#include "VertexFactory.h"
#include "Engine/CollisionProfile.h"
#include "Engine/TextureRenderTarget2D.h"
#include "Materials/Material.h"
#include "LocalVertexFactory.h"
#include "SceneManagement.h"
#include "DynamicMeshBuilder.h"
#include "EngineGlobals.h"
#include "Engine/Engine.h"
#include "MaterialShared.h"
#include "SceneInterface.h"
#include "TextureResource.h"
#include "MaterialDomain.h"
#include "Materials/MaterialRenderProxy.h"
/** Scene proxy */
class FOculusXRMR_PlaneMeshSceneProxy : public FPrimitiveSceneProxy
{
public:
FOculusXRMR_PlaneMeshSceneProxy(UOculusXRMR_PlaneMeshComponent* Component, UTextureRenderTarget2D* RenderTarget)
: FPrimitiveSceneProxy(Component)
, MaterialRelevance(Component->GetMaterialRelevance(GetScene().GetFeatureLevel()))
, PlaneRenderTarget(RenderTarget)
{
const FColor VertexColor(255, 255, 255);
const int32 NumTris = Component->CustomMeshTris.Num();
Vertices.AddUninitialized(NumTris * 3);
Indices.AddUninitialized(NumTris * 3);
// Add each triangle to the vertex/index buffer
for (int32 TriIdx = 0; TriIdx < NumTris; TriIdx++)
{
FOculusXRMR_PlaneMeshTriangle& Tri = Component->CustomMeshTris[TriIdx];
const FVector Edge01 = (Tri.Vertex1 - Tri.Vertex0);
const FVector Edge02 = (Tri.Vertex2 - Tri.Vertex0);
const FVector TangentX = Edge01.GetSafeNormal();
const FVector TangentZ = (Edge02 ^ Edge01).GetSafeNormal();
const FVector TangentY = (TangentX ^ TangentZ).GetSafeNormal();
FDynamicMeshVertex Vert;
Vert.Color = VertexColor;
Vert.SetTangents((FVector3f)TangentX, (FVector3f)TangentY, (FVector3f)TangentZ);
Vert.Position = (FVector3f)Tri.Vertex0;
Vert.TextureCoordinate[0] = FVector2f(Tri.UV0); // LWC_TODO: Precision loss
Vertices[TriIdx * 3 + 0] = Vert;
Indices[TriIdx * 3 + 0] = TriIdx * 3 + 0;
Vert.Position = (FVector3f)Tri.Vertex1;
Vert.TextureCoordinate[0] = FVector2f(Tri.UV1); // LWC_TODO: Precision loss
Vertices[TriIdx * 3 + 1] = Vert;
Indices[TriIdx * 3 + 1] = TriIdx * 3 + 1;
Vert.Position = (FVector3f)Tri.Vertex2;
Vert.TextureCoordinate[0] = FVector2f(Tri.UV2); // LWC_TODO: Precision loss
Vertices[TriIdx * 3 + 2] = Vert;
Indices[TriIdx * 3 + 2] = TriIdx * 3 + 2;
}
// Grab material
Material = Component->GetMaterial(0);
if (Material == nullptr)
{
Material = UMaterial::GetDefaultMaterial(MD_Surface);
}
}
virtual ~FOculusXRMR_PlaneMeshSceneProxy()
{
}
SIZE_T GetTypeHash() const override
{
static size_t UniquePointer;
return reinterpret_cast<size_t>(&UniquePointer);
}
virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_OculusXRMR_PlaneMeshSceneProxy_GetDynamicMeshElements);
// the mesh is only visible inside the CastingViewport, and the Full CastingLayer (the Composition mode)
if (PlaneRenderTarget && ViewFamily.RenderTarget == PlaneRenderTarget->GetRenderTargetResource())
{
const bool bWireframe = AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe;
FMaterialRenderProxy* MaterialProxy = nullptr;
if (bWireframe)
{
auto WireframeMaterialInstance = new FColoredMaterialRenderProxy(
GEngine->WireframeMaterial->GetRenderProxy(),
FLinearColor(0, 0.5f, 1.f));
Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance);
MaterialProxy = WireframeMaterialInstance;
}
else
{
MaterialProxy = Material->GetRenderProxy();
}
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
if (VisibilityMap & (1 << ViewIndex))
{
const FSceneView* View = Views[ViewIndex];
FDynamicMeshBuilder DynamicMeshBuilder(View->GetFeatureLevel());
DynamicMeshBuilder.AddVertices(Vertices);
DynamicMeshBuilder.AddTriangles(Indices);
DynamicMeshBuilder.GetMesh(GetLocalToWorld(), MaterialProxy, SDPG_World, true, false, ViewIndex, Collector);
// -- Original draw code for reference --
// FMeshBatch& Mesh = Collector.AllocateMesh();
// FMeshBatchElement& BatchElement = Mesh.Elements[0];
// BatchElement.IndexBuffer = &IndexBuffer;
// Mesh.bWireframe = bWireframe;
// Mesh.VertexFactory = &VertexFactory;
// Mesh.MaterialRenderProxy = MaterialProxy;
// BatchElement.PrimitiveUniformBuffer = CreatePrimitiveUniformBufferImmediate(GetLocalToWorld(), GetBounds(), GetLocalBounds(), true, DrawsVelocity());
// BatchElement.FirstIndex = 0;
// BatchElement.NumPrimitives = IndexBuffer.Indices.Num() / 3;
// BatchElement.MinVertexIndex = 0;
// BatchElement.MaxVertexIndex = VertexBuffer.Vertices.Num() - 1;
// Mesh.ReverseCulling = IsLocalToWorldDeterminantNegative();
// Mesh.Type = PT_TriangleList;
// Mesh.DepthPriorityGroup = SDPG_World;
// Mesh.bCanApplyViewModeOverrides = false;
// Collector.AddMesh(ViewIndex, Mesh);
}
}
}
}
virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override
{
FPrimitiveViewRelevance Result;
Result.bDrawRelevance = IsShown(View);
Result.bShadowRelevance = IsShadowCast(View);
Result.bDynamicRelevance = true;
Result.bRenderInMainPass = ShouldRenderInMainPass();
Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask();
Result.bRenderCustomDepth = ShouldRenderCustomDepth();
MaterialRelevance.SetPrimitiveViewRelevance(Result);
return Result;
}
virtual bool CanBeOccluded() const override
{
return !MaterialRelevance.bDisableDepthTest;
}
virtual uint32 GetMemoryFootprint(void) const override { return (sizeof(*this) + GetAllocatedSize()); }
uint32 GetAllocatedSize(void) const { return (FPrimitiveSceneProxy::GetAllocatedSize()); }
private:
UMaterialInterface* Material;
TArray<FDynamicMeshVertex> Vertices;
TArray<uint32> Indices;
FMaterialRelevance MaterialRelevance;
UTextureRenderTarget2D* PlaneRenderTarget;
};
//////////////////////////////////////////////////////////////////////////
UOculusXRMR_PlaneMeshComponent::UOculusXRMR_PlaneMeshComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
PrimaryComponentTick.bCanEverTick = false;
SetCollisionProfileName(UCollisionProfile::BlockAllDynamic_ProfileName);
bRenderCustomDepth = true;
}
bool UOculusXRMR_PlaneMeshComponent::SetCustomMeshTriangles(const TArray<FOculusXRMR_PlaneMeshTriangle>& Triangles)
{
CustomMeshTris = Triangles;
// Need to recreate scene proxy to send it over
MarkRenderStateDirty();
return true;
}
void UOculusXRMR_PlaneMeshComponent::AddCustomMeshTriangles(const TArray<FOculusXRMR_PlaneMeshTriangle>& Triangles)
{
CustomMeshTris.Append(Triangles);
// Need to recreate scene proxy to send it over
MarkRenderStateDirty();
}
void UOculusXRMR_PlaneMeshComponent::ClearCustomMeshTriangles()
{
CustomMeshTris.Reset();
// Need to recreate scene proxy to send it over
MarkRenderStateDirty();
}
void UOculusXRMR_PlaneMeshComponent::Place(const FVector& Center, const FVector& Up, const FVector& Normal, const FVector2D& Size)
{
FVector Right = FVector::CrossProduct(Up, Normal);
FVector Up_N = Up.GetUnsafeNormal();
FVector Right_N = Right.GetUnsafeNormal();
FVector V0 = Center - Right_N * Size.X * 0.5f - Up_N * Size.Y * 0.5f;
FVector2D UV0(1, 1);
FVector V1 = Center + Right_N * Size.X * 0.5f - Up_N * Size.Y * 0.5f;
FVector2D UV1(0, 1);
FVector V2 = Center - Right_N * Size.X * 0.5f + Up_N * Size.Y * 0.5f;
FVector2D UV2(1, 0);
FVector V3 = Center + Right_N * Size.X * 0.5f + Up_N * Size.Y * 0.5f;
FVector2D UV3(0, 0);
FOculusXRMR_PlaneMeshTriangle Tri0, Tri1;
Tri0.Vertex0 = V1;
Tri0.UV0 = UV1;
Tri0.Vertex1 = V0;
Tri0.UV1 = UV0;
Tri0.Vertex2 = V2;
Tri0.UV2 = UV2;
Tri1.Vertex0 = V1;
Tri1.UV0 = UV1;
Tri1.Vertex1 = V2;
Tri1.UV1 = UV2;
Tri1.Vertex2 = V3;
Tri1.UV2 = UV3;
SetCustomMeshTriangles({ Tri0, Tri1 });
}
FPrimitiveSceneProxy* UOculusXRMR_PlaneMeshComponent::CreateSceneProxy()
{
FPrimitiveSceneProxy* Proxy = nullptr;
if (CustomMeshTris.Num() > 0)
{
Proxy = new FOculusXRMR_PlaneMeshSceneProxy(this, PlaneRenderTarget);
}
return Proxy;
}
int32 UOculusXRMR_PlaneMeshComponent::GetNumMaterials() const
{
return 1;
}
FBoxSphereBounds UOculusXRMR_PlaneMeshComponent::CalcBounds(const FTransform& LocalToWorld) const
{
FBoxSphereBounds NewBounds;
NewBounds.Origin = FVector::ZeroVector;
NewBounds.BoxExtent = FVector(HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX);
NewBounds.SphereRadius = FMath::Sqrt(3.0f * FMath::Square(HALF_WORLD_MAX));
return NewBounds;
}

View File

@@ -0,0 +1,77 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "UObject/ObjectMacros.h"
#include "Components/MeshComponent.h"
#include "OculusXRMR_PlaneMeshComponent.generated.h"
class FPrimitiveSceneProxy;
class UTextureRenderTarget2D;
USTRUCT(BlueprintType)
struct FOculusXRMR_PlaneMeshTriangle
{
GENERATED_USTRUCT_BODY()
UPROPERTY()
FVector Vertex0 = FVector(0.0f);
UPROPERTY()
FVector2D UV0 = FVector2D(0.0f);
UPROPERTY()
FVector Vertex1 = FVector(0.0f);
UPROPERTY()
FVector2D UV1 = FVector2D(0.0f);
UPROPERTY()
FVector Vertex2 = FVector(0.0f);
UPROPERTY()
FVector2D UV2 = FVector2D(0.0f);
};
/** Component that allows you to specify custom triangle mesh geometry */
UCLASS(hidecategories = (Object, LOD, Physics, Collision), editinlinenew, ClassGroup = Rendering, NotPlaceable, NotBlueprintable)
class UOculusXRMR_PlaneMeshComponent : public UMeshComponent
{
GENERATED_UCLASS_BODY()
/** Set the geometry to use on this triangle mesh */
UFUNCTION(BlueprintCallable, Category = "Components|CustomMesh")
bool SetCustomMeshTriangles(const TArray<FOculusXRMR_PlaneMeshTriangle>& Triangles);
/** Add to the geometry to use on this triangle mesh. This may cause an allocation. Use SetCustomMeshTriangles() instead when possible to reduce allocations. */
UFUNCTION(BlueprintCallable, Category = "Components|CustomMesh")
void AddCustomMeshTriangles(const TArray<FOculusXRMR_PlaneMeshTriangle>& Triangles);
/** Removes all geometry from this triangle mesh. Does not deallocate memory, allowing new geometry to reuse the existing allocation. */
UFUNCTION(BlueprintCallable, Category = "Components|CustomMesh")
void ClearCustomMeshTriangles();
void Place(const FVector& Center, const FVector& Up, const FVector& Normal, const FVector2D& Size);
void SetPlaneRenderTarget(UTextureRenderTarget2D* RT) { PlaneRenderTarget = RT; }
private:
//~ Begin UPrimitiveComponent Interface.
virtual FPrimitiveSceneProxy* CreateSceneProxy() override;
//~ End UPrimitiveComponent Interface.
//~ Begin UMeshComponent Interface.
virtual int32 GetNumMaterials() const override;
//~ End UMeshComponent Interface.
//~ Begin USceneComponent Interface.
virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override;
//~ Begin USceneComponent Interface.
TArray<FOculusXRMR_PlaneMeshTriangle> CustomMeshTris;
UTextureRenderTarget2D* PlaneRenderTarget;
friend class FOculusXRMR_PlaneMeshSceneProxy;
};

View File

@@ -0,0 +1,142 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#include "OculusXRMR_Settings.h"
#include "OculusXRMRPrivate.h"
#include "OculusXRHMD.h"
#include "Engine/Engine.h"
UOculusXRMR_Settings::UOculusXRMR_Settings(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, ClippingReference(EOculusXRMR_ClippingReference::CR_Head)
, bUseTrackedCameraResolution(true)
, WidthPerView(960)
, HeightPerView(540)
, CastingLatency(0.0f)
, BackdropColor(FColor::Green)
, ExternalCompositionPostProcessEffects(EOculusXRMR_PostProcessEffects::PPE_Off)
, bIsCasting(false)
, CompositionMethod(EOculusXRMR_CompositionMethod::ExternalComposition)
, BindToTrackedCameraIndex(-1)
{
}
void UOculusXRMR_Settings::SetCompositionMethod(EOculusXRMR_CompositionMethod val)
{
if (CompositionMethod == val)
{
return;
}
auto old = CompositionMethod;
CompositionMethod = val;
CompositionMethodChangeDelegate.Execute(old, val);
}
void UOculusXRMR_Settings::SetCapturingCamera(EOculusXRMR_CameraDeviceEnum val)
{
// deprecated
}
void UOculusXRMR_Settings::SetIsCasting(bool val)
{
if (bIsCasting == val)
{
return;
}
auto old = bIsCasting;
bIsCasting = val;
IsCastingChangeDelegate.Execute(old, val);
}
void UOculusXRMR_Settings::BindToTrackedCameraIndexIfAvailable(int InTrackedCameraIndex)
{
if (BindToTrackedCameraIndex == InTrackedCameraIndex)
{
return;
}
auto old = BindToTrackedCameraIndex;
BindToTrackedCameraIndex = InTrackedCameraIndex;
TrackedCameraIndexChangeDelegate.Execute(old, InTrackedCameraIndex);
}
void UOculusXRMR_Settings::LoadFromIni()
{
if (!GConfig)
{
UE_LOG(LogMR, Warning, TEXT("GConfig is NULL"));
return;
}
// Flushing the GEngineIni is necessary to get the settings reloaded at the runtime, but the manual flushing
// could cause an assert when loading audio settings if launching through editor at the 2nd time. Disabled temporarily.
// GConfig->Flush(true, GEngineIni);
const TCHAR* OculusXRMRSettings = TEXT("Oculus.Settings.MixedReality");
bool v;
float f;
int32 i;
FColor color;
if (GConfig->GetInt(OculusXRMRSettings, TEXT("CompositionMethod"), i, GEngineIni))
{
SetCompositionMethod((EOculusXRMR_CompositionMethod)i);
}
if (GConfig->GetInt(OculusXRMRSettings, TEXT("ClippingReference"), i, GEngineIni))
{
ClippingReference = (EOculusXRMR_ClippingReference)i;
}
if (GConfig->GetBool(OculusXRMRSettings, TEXT("bUseTrackedCameraResolution"), v, GEngineIni))
{
bUseTrackedCameraResolution = v;
}
if (GConfig->GetInt(OculusXRMRSettings, TEXT("WidthPerView"), i, GEngineIni))
{
WidthPerView = i;
}
if (GConfig->GetInt(OculusXRMRSettings, TEXT("HeightPerView"), i, GEngineIni))
{
HeightPerView = i;
}
if (GConfig->GetFloat(OculusXRMRSettings, TEXT("CastingLatency"), f, GEngineIni))
{
CastingLatency = f;
}
if (GConfig->GetColor(OculusXRMRSettings, TEXT("BackdropColor"), color, GEngineIni))
{
BackdropColor = color;
}
if (GConfig->GetInt(OculusXRMRSettings, TEXT("BindToTrackedCameraIndex"), i, GEngineIni))
{
BindToTrackedCameraIndexIfAvailable(i);
}
if (GConfig->GetInt(OculusXRMRSettings, TEXT("ExternalCompositionPostProcessEffects"), i, GEngineIni))
{
ExternalCompositionPostProcessEffects = (EOculusXRMR_PostProcessEffects)i;
}
UE_LOG(LogMR, Log, TEXT("MixedReality settings loaded from Engine.ini"));
}
void UOculusXRMR_Settings::SaveToIni() const
{
if (!GConfig)
{
UE_LOG(LogMR, Warning, TEXT("GConfig is NULL"));
return;
}
const TCHAR* OculusXRMRSettings = TEXT("Oculus.Settings.MixedReality");
GConfig->SetInt(OculusXRMRSettings, TEXT("CompositionMethod"), (int32)CompositionMethod, GEngineIni);
GConfig->SetInt(OculusXRMRSettings, TEXT("ClippingReference"), (int32)ClippingReference, GEngineIni);
GConfig->SetBool(OculusXRMRSettings, TEXT("bUseTrackedCameraResolution"), bUseTrackedCameraResolution, GEngineIni);
GConfig->SetInt(OculusXRMRSettings, TEXT("WidthPerView"), WidthPerView, GEngineIni);
GConfig->SetInt(OculusXRMRSettings, TEXT("HeightPerView"), HeightPerView, GEngineIni);
GConfig->SetFloat(OculusXRMRSettings, TEXT("CastingLatency"), CastingLatency, GEngineIni);
GConfig->SetColor(OculusXRMRSettings, TEXT("BackdropColor"), BackdropColor, GEngineIni);
GConfig->SetInt(OculusXRMRSettings, TEXT("BindToTrackedCameraIndex"), (int32)BindToTrackedCameraIndex, GEngineIni);
GConfig->SetInt(OculusXRMRSettings, TEXT("ExternalCompositionPostProcessEffects"), (int32)ExternalCompositionPostProcessEffects, GEngineIni);
GConfig->Flush(false, GEngineIni);
UE_LOG(LogMR, Log, TEXT("MixedReality settings saved to Engine.ini"));
}

View File

@@ -0,0 +1,13 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#include "OculusXRMR_State.h"
#include "OculusXRMRFunctionLibrary.h"
UOculusXRMR_State::UOculusXRMR_State(const FObjectInitializer& ObjectInitializer)
: TrackedCamera()
, TrackingReferenceComponent(nullptr)
, ScalingFactor(1.0f)
, ChangeCameraStateRequested(false)
, BindToTrackedCameraIndexRequested(false)
{
}

View File

@@ -0,0 +1,117 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "UObject/ObjectMacros.h"
#include "OculusXRFunctionLibrary.h"
#include "OculusXRPluginWrapper.h"
#include "OculusXRMR_State.generated.h"
USTRUCT()
struct FOculusXRTrackedCamera
{
GENERATED_USTRUCT_BODY()
/** >=0: the index of the external camera
* -1: not bind to any external camera (and would be setup to match the manual CastingCameraActor placement)
*/
UPROPERTY()
int32 Index;
/** The external camera name set through the CameraTool */
UPROPERTY()
FString Name;
/** The time that this camera was updated */
UPROPERTY()
double UpdateTime;
/** The horizontal FOV, in degrees */
UPROPERTY(meta = (UIMin = "5.0", UIMax = "170", ClampMin = "0.001", ClampMax = "360.0", Units = deg))
float FieldOfView;
/** The resolution of the camera frame */
UPROPERTY()
int32 SizeX;
/** The resolution of the camera frame */
UPROPERTY()
int32 SizeY;
/** The tracking node the external camera is bound to */
UPROPERTY()
EOculusXRTrackedDeviceType AttachedTrackedDevice;
/** The relative pose of the camera to the attached tracking device */
UPROPERTY()
FRotator CalibratedRotation;
/** The relative pose of the camera to the attached tracking device */
UPROPERTY()
FVector CalibratedOffset;
/** (optional) The user pose is provided to fine tuning the relative camera pose at the run-time */
UPROPERTY()
FRotator UserRotation;
/** (optional) The user pose is provided to fine tuning the relative camera pose at the run-time */
UPROPERTY()
FVector UserOffset;
/** The raw pose of the camera to the attached tracking device (Deprecated) */
UPROPERTY(meta = (DeprecatedProperty, DeprecationMessage = "All camera pose info is now in stage space, do not use raw pose data."))
FRotator RawRotation_DEPRECATED;
/** The raw pose of the camera to the attached tracking device (Deprecated) */
UPROPERTY(meta = (DeprecatedProperty, DeprecationMessage = "All camera pose info is now in stage space, do not use raw pose data."))
FVector RawOffset_DEPRECATED;
FOculusXRTrackedCamera()
: Index(-1)
, Name(TEXT("Unknown"))
, UpdateTime(0.0f)
, FieldOfView(90.0f)
, SizeX(1280)
, SizeY(720)
, AttachedTrackedDevice(EOculusXRTrackedDeviceType::None)
, CalibratedRotation(EForceInit::ForceInitToZero)
, CalibratedOffset(EForceInit::ForceInitToZero)
, UserRotation(EForceInit::ForceInitToZero)
, UserOffset(EForceInit::ForceInitToZero)
, RawRotation_DEPRECATED(EForceInit::ForceInitToZero)
, RawOffset_DEPRECATED(EForceInit::ForceInitToZero)
{
}
};
/**
* Object to hold the state of MR capture and capturing camera
*/
UCLASS(ClassGroup = OculusXRMR, NotPlaceable, NotBlueprintable)
class UOculusXRMR_State : public UObject
{
GENERATED_BODY()
public:
UOculusXRMR_State(const FObjectInitializer& ObjectInitializer);
UPROPERTY()
FOculusXRTrackedCamera TrackedCamera;
// Component at the tracking origin that the camera calibration is applied to
UPROPERTY()
class USceneComponent* TrackingReferenceComponent;
// A multiplier on the camera distance, should be based on the scaling of the player component
UPROPERTY()
double ScalingFactor;
/** Flag indicating a change in the tracked camera state for the camera actor to consume */
UPROPERTY()
bool ChangeCameraStateRequested;
/** Flag indicating a change in the tracked camera index for the camera actor to consume */
UPROPERTY()
bool BindToTrackedCameraIndexRequested;
};

View File

@@ -0,0 +1,38 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Modules/ModuleManager.h"
// Oculus support is not available on Windows XP
#define OCULUS_MR_SUPPORTED_PLATFORMS ((PLATFORM_WINDOWS && WINVER > 0x0502) || PLATFORM_ANDROID)
/**
* The public interface to this module. In most cases, this interface is only public to sibling modules
* within this plugin.
*/
class IOculusXRMRModule : public IModuleInterface
{
public:
/**
* Singleton-like access to this module's interface. This is just for convenience!
* Beware of calling this during the shutdown phase, though. Your module might have been unloaded already.
*
* @return Returns singleton instance, loading the module on demand if needed
*/
static inline IOculusXRMRModule& Get()
{
return FModuleManager::GetModuleChecked<IOculusXRMRModule>("OculusXRMR");
}
/**
* Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true.
*
* @return True if the module is loaded and ready to use
*/
static inline bool IsAvailable()
{
return FModuleManager::Get().IsModuleLoaded("OculusXRMR");
}
};

View File

@@ -0,0 +1,59 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "UObject/ObjectMacros.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "OculusXRMRFunctionLibrary.generated.h"
class USceneComponent;
class UOculusXRMR_Settings;
struct FOculusXRTrackedCamera;
namespace OculusXRHMD
{
class FOculusXRHMD;
}
UCLASS()
class OCULUSXRMR_API UOculusXRMRFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_UCLASS_BODY()
public:
// Get the OculusXRMR settings object
UFUNCTION(BlueprintCallable, Category = "OculusLibrary|MR", meta = (DisplayName = "Get Oculus MR Settings"))
static UOculusXRMR_Settings* GetOculusXRMRSettings();
// Get the component that the OculusXRMR camera is tracking. When this is null, the camera will track the player pawn.
UFUNCTION(BlueprintCallable, Category = "OculusLibrary|MR")
static USceneComponent* GetTrackingReferenceComponent();
// Set the component for the OculusXRMR camera to track. If this is set to null, the camera will track the player pawn.
UFUNCTION(BlueprintCallable, Category = "OculusLibrary|MR")
static bool SetTrackingReferenceComponent(USceneComponent* Component);
// Get the scaling factor for the MRC configuration. Returns 0 if not available.
UFUNCTION(BlueprintCallable, Category = "OculusLibrary|MR", meta = (DisplayName = "Get MRC Scaling Factor"))
static float GetMrcScalingFactor();
// Set the scaling factor for the MRC configuration. This should be a positive value set to the same scaling as the VR player pawn so that the game capture and camera video are aligned.
UFUNCTION(BlueprintCallable, Category = "OculusLibrary|MR", meta = (DisplayName = "Set MRC Scaling Factor"))
static bool SetMrcScalingFactor(float ScalingFactor = 1.0f);
// Check if MRC is enabled
UFUNCTION(BlueprintCallable, Category = "OculusLibrary|MR")
static bool IsMrcEnabled();
// Check if MRC is enabled and actively capturing
UFUNCTION(BlueprintCallable, Category = "OculusLibrary|MR")
static bool IsMrcActive();
public:
static class TSharedPtr<class IXRTrackingSystem, ESPMode::ThreadSafe> GetTrackingSystem();
/** Retrieve an array of all (calibrated) tracked cameras which were calibrated through the CameraTool */
static void GetAllTrackedCamera(TArray<FOculusXRTrackedCamera>& TrackedCameras, bool bCalibratedOnly = true);
static bool GetTrackingReferenceLocationAndRotationInWorldSpace(USceneComponent* TrackingReferenceComponent, FVector& TRLocation, FRotator& TRRotation);
};

View File

@@ -0,0 +1,175 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "UObject/ObjectMacros.h"
#include "OculusXRMR_Settings.generated.h"
UENUM(BlueprintType)
enum class EOculusXRMR_CameraDeviceEnum : uint8 // Deprecated
{
CD_None_DEPRECATED UMETA(DisplayName = "None"),
CD_WebCamera0_DEPRECATED UMETA(DisplayName = "Web Camera 0"),
CD_WebCamera1_DEPRECATED UMETA(DisplayName = "Web Camera 1"),
};
UENUM(BlueprintType)
enum class EOculusXRMR_ClippingReference : uint8
{
CR_TrackingReference UMETA(DisplayName = "Tracking Reference"),
CR_Head UMETA(DisplayName = "Head"),
};
UENUM(BlueprintType)
enum class EOculusXRMR_PostProcessEffects : uint8
{
PPE_Off UMETA(DisplayName = "Off"),
PPE_On UMETA(DisplayName = "On"),
};
UENUM(BlueprintType)
enum class EOculusXRMR_CompositionMethod : uint8
{
/* Generate both foreground and background views for compositing with 3rd-party software like OBS. */
ExternalComposition UMETA(DisplayName = "External Composition"),
/* (Deprecated) Composite the camera stream directly to the output with the proper depth.*/
DirectComposition_DEPRECATED UMETA(DisplayName = "Direct Composition (DEPRECATED)")
};
UCLASS(ClassGroup = OculusXRMR, Blueprintable)
class UOculusXRMR_Settings : public UObject
{
GENERATED_BODY()
public:
UOculusXRMR_Settings(const FObjectInitializer& ObjectInitializer);
/** Specify the distance to the camera which divide the background and foreground in MxR casting.
* Set it to CR_TrackingReference to use the distance to the Tracking Reference, which works better
* in the stationary experience. Set it to CR_Head would use the distance to the HMD, which works better
* in the room scale experience.
*/
UPROPERTY(Category = MetaXR, EditAnywhere, BlueprintReadWrite)
EOculusXRMR_ClippingReference ClippingReference;
/** The casting viewports would use the same resolution of the camera which used in the calibration process. */
UPROPERTY(Category = MetaXR, EditAnywhere, BlueprintReadWrite)
bool bUseTrackedCameraResolution;
/** When bUseTrackedCameraResolution is false, the width of each casting viewport */
UPROPERTY(Category = MetaXR, EditAnywhere, BlueprintReadWrite)
int WidthPerView;
/** When bUseTrackedCameraResolution is false, the height of each casting viewport */
UPROPERTY(Category = MetaXR, EditAnywhere, BlueprintReadWrite)
int HeightPerView;
/** When CompositionMethod is External Composition, the latency of the casting output which could be adjusted to
* match the camera latency in the external composition application */
UPROPERTY(Category = MetaXR, EditAnywhere, BlueprintReadWrite, meta = (UIMin = "0.0", UIMax = "0.1"))
float CastingLatency;
/** When CompositionMethod is External Composition, the color of the backdrop in the foreground view */
UPROPERTY(Category = MetaXR, EditAnywhere, BlueprintReadWrite)
FColor BackdropColor;
/** When CompositionMethod is Direct Composition, you could adjust this latency to delay the virtual
* hand movement by a small amount of time to match the camera latency */
UPROPERTY(meta = (DeprecatedProperty, DeprecationMessage = "Direct Composition deprecated."))
float HandPoseStateLatency_DEPRECATED;
/** [Green-screen removal] Chroma Key Color. Apply when CompositionMethod is DirectComposition */
UPROPERTY(meta = (DeprecatedProperty, DeprecationMessage = "Direct Composition deprecated."))
FColor ChromaKeyColor_DEPRECATED;
/** [Green-screen removal] Chroma Key Similarity. Apply when CompositionMethod is DirectComposition */
UPROPERTY(meta = (DeprecatedProperty, DeprecationMessage = "Direct Composition deprecated."))
float ChromaKeySimilarity_DEPRECATED;
/** [Green-screen removal] Chroma Key Smooth Range. Apply when CompositionMethod is DirectComposition */
UPROPERTY(meta = (DeprecatedProperty, DeprecationMessage = "Direct Composition deprecated."))
float ChromaKeySmoothRange_DEPRECATED;
/** [Green-screen removal] Chroma Key Spill Range. Apply when CompositionMethod is DirectComposition */
UPROPERTY(meta = (DeprecatedProperty, DeprecationMessage = "Direct Composition deprecated."))
float ChromaKeySpillRange_DEPRECATED;
/** Set the amount of post process effects in the MR view for external composition */
UPROPERTY(Category = MetaXR, EditAnywhere, BlueprintReadWrite)
EOculusXRMR_PostProcessEffects ExternalCompositionPostProcessEffects;
/** ExternalComposition: The casting window includes the background and foreground view
* DirectComposition: The game scene would be composited with the camera frame directly
*/
UFUNCTION(BlueprintCallable, Category = MetaXR)
EOculusXRMR_CompositionMethod GetCompositionMethod() { return CompositionMethod; }
/** ExternalComposition: The casting window includes the background and foreground view
* DirectComposition: The game scene would be composited with the camera frame directly
*/
UFUNCTION(BlueprintCallable, Category = MetaXR)
void SetCompositionMethod(EOculusXRMR_CompositionMethod val);
/** When CompositionMethod is DirectComposition, the physical camera device which provide the frame */
UFUNCTION(BlueprintCallable, Category = MetaXR, meta = (DeprecatedFunction, DeprecationMessage = "Direct Composition deprecated."))
EOculusXRMR_CameraDeviceEnum GetCapturingCamera() { return EOculusXRMR_CameraDeviceEnum::CD_None_DEPRECATED; }
/** When CompositionMethod is DirectComposition, the physical camera device which provide the frame */
UFUNCTION(BlueprintCallable, Category = MetaXR, meta = (DeprecatedFunction, DeprecationMessage = "Direct Composition deprecated."))
void SetCapturingCamera(EOculusXRMR_CameraDeviceEnum val);
/** Is MRC on and off */
UFUNCTION(BlueprintCallable, Category = MetaXR)
bool GetIsCasting() { return bIsCasting; }
/** Turns MRC on and off */
UFUNCTION(BlueprintCallable, Category = MetaXR)
void SetIsCasting(bool val);
/** Bind the casting camera to the calibrated external camera.
* (Requires a calibrated external camera)
*/
UFUNCTION(BlueprintCallable, Category = MetaXR)
void BindToTrackedCameraIndexIfAvailable(int InTrackedCameraIndex);
UFUNCTION(BlueprintCallable, Category = MetaXR)
int GetBindToTrackedCameraIndex() { return BindToTrackedCameraIndex; }
/** Load settings from the config file */
UFUNCTION(BlueprintCallable, Category = MetaXR)
void LoadFromIni();
/** Save settings to the config file */
UFUNCTION(BlueprintCallable, Category = MetaXR)
void SaveToIni() const;
private:
/** Turns MRC on and off (does not get saved to or loaded from ini) */
UPROPERTY()
bool bIsCasting;
/** ExternalComposition: The casting window includes the background and foreground view
* DirectComposition: The game scene would be composited with the camera frame directly
*/
UPROPERTY()
EOculusXRMR_CompositionMethod CompositionMethod;
/** When CompositionMethod is DirectComposition, the physical camera device which provide the frame */
UPROPERTY(meta = (DeprecatedProperty, DeprecationMessage = "Direct Composition deprecated."))
EOculusXRMR_CameraDeviceEnum CapturingCamera_DEPRECATED;
/** Tracked camera that we want to bind the in-game MR camera to*/
int BindToTrackedCameraIndex;
DECLARE_DELEGATE_TwoParams(OnCompositionMethodChangeDelegate, EOculusXRMR_CompositionMethod, EOculusXRMR_CompositionMethod);
DECLARE_DELEGATE_TwoParams(OnBooleanSettingChangeDelegate, bool, bool);
DECLARE_DELEGATE_TwoParams(OnIntegerSettingChangeDelegate, int, int);
OnIntegerSettingChangeDelegate TrackedCameraIndexChangeDelegate;
OnCompositionMethodChangeDelegate CompositionMethodChangeDelegate;
OnBooleanSettingChangeDelegate IsCastingChangeDelegate;
// Give the OculusXRMR module access to the delegates so that
friend class FOculusXRMRModule;
};