// @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 CEnableExternalCompositionPostProcess(TEXT("oculus.mr.ExternalCompositionPostProcess"), 0, TEXT("Enable MR external composition post process: 0=Off, 1=On")); static TAutoConsoleVariable 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(TEXT("VRNotificationComponent")); #if PLATFORM_WINDOWS PlaneMeshComponent = CreateDefaultSubobject(TEXT("PlaneMeshComponent")); PlaneMeshComponent->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform); PlaneMeshComponent->ResetRelativeTransform(); PlaneMeshComponent->SetVisibility(false); #endif OpaqueColoredMaterial = Cast(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 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(); BackgroundRenderTargets[0]->RenderTargetFormat = RTF_RGBA8_SRGB; ForegroundRenderTargets[0] = NewObject(); 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(); BackgroundRenderTargets[i]->RenderTargetFormat = RTF_RGBA8_SRGB; ForegroundRenderTargets[i] = NewObject(); 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 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(); // 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