658 lines
23 KiB
C++

// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRPassthroughLayer.h"
#include "Components/MeshComponent.h"
#include "Engine/Engine.h"
#include "Materials/Material.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Materials/MaterialInterface.h"
#include "OculusXRHMDPrivate.h"
#include "OculusXRPassthroughXRFunctions.h"
#include "OculusXRPassthroughXR.h"
#include "OculusXRPassthroughModule.h"
#include "ProceduralMeshComponent.h"
#include "XRThreadUtils.h"
namespace XRPassthrough
{
static UWorld* GetWorld()
{
UWorld* World = nullptr;
for (const FWorldContext& Context : GEngine->GetWorldContexts())
{
if (Context.WorldType == EWorldType::Game || Context.WorldType == EWorldType::PIE)
{
World = Context.World();
}
}
return World;
}
FPassthroughLayer::FPassthroughLayer(XrPassthroughFB PassthroughInstance, TWeakPtr<FPassthroughXR> Extension)
: PassthroughExtension(Extension)
, UserDefinedGeometryMap(nullptr)
, PassthroughPokeActorMap(nullptr)
, XrPassthroughLayer{ XR_NULL_HANDLE }
, XrCompositionLayerHeader{}
, XrPassthroughInstance(PassthroughInstance)
{
}
FPassthroughLayer::FPassthroughLayer(const FPassthroughLayer& Layer)
: PassthroughExtension(Layer.PassthroughExtension)
, UserDefinedGeometryMap(Layer.UserDefinedGeometryMap)
, PassthroughPokeActorMap(Layer.PassthroughPokeActorMap)
, Session(Layer.Session)
, LayerDesc(Layer.LayerDesc)
, XrPassthroughLayer(Layer.XrPassthroughLayer)
, XrCompositionLayerHeader(Layer.XrCompositionLayerHeader)
, XrPassthroughInstance(Layer.XrPassthroughInstance)
{
}
TSharedPtr<FPassthroughLayer, ESPMode::ThreadSafe> FPassthroughLayer::Clone() const
{
return MakeShareable(new FPassthroughLayer(*this));
}
FPassthroughLayer::~FPassthroughLayer()
{
}
void FPassthroughLayer::SetDesc(const IStereoLayers::FLayerDesc& InLayerDesc)
{
LayerDesc = InLayerDesc;
if (!PassthroughPokeActorMap)
{
PassthroughPokeActorMap = MakeShared<TMap<FString, FPassthroughPokeActor>, ESPMode::ThreadSafe>();
}
UpdatePassthroughPokeActors_GameThread();
}
void FPassthroughLayer::DestroyLayer()
{
OculusXRHMD::CheckInGameThread();
ClearPassthroughPokeActors();
}
void FPassthroughLayer::DestroyLayer_RenderThread()
{
OculusXRHMD::CheckInRenderThread();
// Clear user defined meshes
for (auto& Entry : *UserDefinedGeometryMap)
{
const XrTriangleMeshFB MeshHandle = Entry.Value.MeshHandle;
const XrGeometryInstanceFB InstanceHandle = Entry.Value.InstanceHandle;
RemovePassthroughMesh_RenderThread(MeshHandle, InstanceHandle);
}
UserDefinedGeometryMap->Empty();
// Destroy passthrough layer
if (XrPassthroughLayer != XR_NULL_HANDLE)
{
xrDestroyPassthroughLayerFB(XrPassthroughLayer);
}
else
{
UE_LOG(LogOculusXRPassthrough, Warning, TEXT("Failed to destroy layer as handle was null"));
}
}
bool FPassthroughLayer::IsPassthoughLayerDesc(const IStereoLayers::FLayerDesc& LayerDesc)
{
return LayerDesc.HasShape<FReconstructedLayer>() || LayerDesc.HasShape<FUserDefinedLayer>();
}
bool FPassthroughLayer::CanReuseResources(const FPassthroughLayer* InLayer) const
{
if (!InLayer)
{
return false;
}
if (!IsPassthoughLayerDesc(InLayer->LayerDesc) || InLayer->LayerDesc.HasShape<FReconstructedLayer>() != LayerDesc.HasShape<FReconstructedLayer>() || InLayer->LayerDesc.HasShape<FUserDefinedLayer>() != LayerDesc.HasShape<FUserDefinedLayer>())
{
return false;
}
return true;
}
bool FPassthroughLayer::Initialize_RenderThread(XrSession InSession, const FPassthroughLayer* InLayer)
{
OculusXRHMD::CheckInRenderThread();
if (!CanReuseResources(InLayer))
{
Session = InSession;
if (XrPassthroughLayer != XR_NULL_HANDLE)
{
xrDestroyPassthroughLayerFB(XrPassthroughLayer);
XrPassthroughLayer = XR_NULL_HANDLE;
}
if (LayerDesc.HasShape<FReconstructedLayer>() || LayerDesc.HasShape<FUserDefinedLayer>())
{
XrPassthroughLayerCreateInfoFB PassthroughLayerCreateInfo = { XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB };
PassthroughLayerCreateInfo.passthrough = XrPassthroughInstance;
PassthroughLayerCreateInfo.purpose = LayerDesc.HasShape<FReconstructedLayer>() ? XR_PASSTHROUGH_LAYER_PURPOSE_RECONSTRUCTION_FB : XR_PASSTHROUGH_LAYER_PURPOSE_PROJECTED_FB;
XrResult CreateLayerResult = xrCreatePassthroughLayerFB(Session, &PassthroughLayerCreateInfo, &XrPassthroughLayer);
if (!XR_SUCCEEDED(CreateLayerResult))
{
UE_LOG(LogOculusXRPassthrough, Warning, TEXT("Failed to create passthrough layer, error : %i"), CreateLayerResult);
return false;
}
XrResult ResumeLayerResult = xrPassthroughLayerResumeFB(XrPassthroughLayer);
if (!XR_SUCCEEDED(ResumeLayerResult))
{
UE_LOG(LogOculusXRPassthrough, Warning, TEXT("Failed to resume passthrough layer, error : %i"), ResumeLayerResult);
return false;
}
}
}
else
{
PassthroughExtension = InLayer->PassthroughExtension;
UserDefinedGeometryMap = InLayer->UserDefinedGeometryMap;
PassthroughPokeActorMap = InLayer->PassthroughPokeActorMap;
Session = InLayer->Session;
XrPassthroughLayer = InLayer->XrPassthroughLayer;
XrCompositionLayerHeader = InLayer->XrCompositionLayerHeader;
XrPassthroughInstance = InLayer->XrPassthroughInstance;
}
if (!UserDefinedGeometryMap)
{
UserDefinedGeometryMap = MakeShared<TMap<FString, FPassthroughMesh>, ESPMode::ThreadSafe>();
}
check(IsPassthoughLayerDesc(LayerDesc));
return true;
}
bool FPassthroughLayer::BuildPassthroughPokeActor(OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh, FPassthroughPokeActor& OutPassthroughPokeActor)
{
UWorld* World = GetWorld();
if (!World || !PassthroughExtension.IsValid())
{
return false;
}
const FString BaseComponentName = FString::Printf(TEXT("OculusPassthroughPoke_%d"), LayerDesc.Id);
const FName ComponentName(*BaseComponentName);
AActor* PassthoughPokeActor = World->SpawnActor<AActor>();
UProceduralMeshComponent* PassthoughPokeComponentPtr = NewObject<UProceduralMeshComponent>(PassthoughPokeActor, ComponentName);
PassthoughPokeComponentPtr->RegisterComponent();
const TArray<int32>& Triangles = PassthroughMesh->GetTriangles();
const TArray<FVector>& Vertices = PassthroughMesh->GetVertices();
TArray<FVector> Normals;
TArray<FVector2D> UV0;
TArray<FLinearColor> VertexColors;
TArray<FProcMeshTangent> Tangents;
PassthoughPokeComponentPtr->CreateMeshSection_LinearColor(0, Vertices, Triangles, Normals, UV0, VertexColors, Tangents, false);
UMaterial* PokeAHoleMaterial = PassthroughExtension.Pin()->GetSettings()->PokeAHoleMaterial;
if (PokeAHoleMaterial)
{
UMaterialInstanceDynamic* DynamicMaterial = UMaterialInstanceDynamic::Create(PokeAHoleMaterial, nullptr);
PassthoughPokeComponentPtr->SetMaterial(0, DynamicMaterial);
}
OutPassthroughPokeActor.PokeAHoleActor = PassthoughPokeActor;
OutPassthroughPokeActor.PokeAHoleComponentPtr = PassthoughPokeComponentPtr;
return true;
}
void FPassthroughLayer::UpdatePassthroughPokeActors_GameThread()
{
if (LayerDesc.HasShape<FUserDefinedLayer>())
{
const FUserDefinedLayer& UserDefinedLayerProps = LayerDesc.GetShape<FUserDefinedLayer>();
const TArray<FUserDefinedGeometryDesc>& UserGeometryList = UserDefinedLayerProps.UserGeometryList;
TSet<FString> UsedSet = {};
if (PassthroughSupportsDepth())
{
for (const FUserDefinedGeometryDesc& GeometryDesc : UserGeometryList)
{
const FString MeshName = GeometryDesc.MeshName;
UsedSet.Add(MeshName);
FPassthroughPokeActor* FoundPassthroughPokeActor = PassthroughPokeActorMap->Find(MeshName);
if (!FoundPassthroughPokeActor)
{
OculusXRHMD::FOculusPassthroughMeshRef GeomPassthroughMesh = GeometryDesc.PassthroughMesh;
if (GeomPassthroughMesh)
{
FPassthroughPokeActor PassthroughPokeActor;
if (BuildPassthroughPokeActor(GeomPassthroughMesh, PassthroughPokeActor))
{
PassthroughPokeActor.PokeAHoleComponentPtr->SetWorldTransform(GeometryDesc.Transform);
PassthroughPokeActorMap->Add(MeshName, PassthroughPokeActor);
}
}
}
else if (GeometryDesc.bUpdateTransform && FoundPassthroughPokeActor->PokeAHoleComponentPtr.IsValid())
{
FoundPassthroughPokeActor->PokeAHoleComponentPtr->SetWorldTransform(GeometryDesc.Transform);
}
}
}
// find actors that no longer exist
TArray<FString> ItemsToRemove;
for (auto& Entry : *PassthroughPokeActorMap)
{
if (!UsedSet.Contains(Entry.Key))
{
ItemsToRemove.Add(Entry.Key);
}
}
for (FString Entry : ItemsToRemove)
{
FPassthroughPokeActor* PassthroughPokeActor = PassthroughPokeActorMap->Find(Entry);
if (PassthroughPokeActor)
{
UWorld* World = GetWorld();
if (World && PassthroughPokeActor->PokeAHoleActor.IsValid())
{
World->DestroyActor(PassthroughPokeActor->PokeAHoleActor.Get());
}
}
PassthroughPokeActorMap->Remove(Entry);
}
}
}
void FPassthroughLayer::UpdatePassthroughStyle_RenderThread(const FEdgeStyleParameters& EdgeStyleParameters)
{
if (!PassthroughExtension.IsValid())
{
return;
}
XrPassthroughStyleFB Style = { XR_TYPE_PASSTHROUGH_STYLE_FB };
Style.textureOpacityFactor = EdgeStyleParameters.TextureOpacityFactor;
Style.edgeColor = { 0, 0, 0, 0 };
if (EdgeStyleParameters.bEnableEdgeColor)
{
Style.edgeColor = {
EdgeStyleParameters.EdgeColor.R,
EdgeStyleParameters.EdgeColor.G,
EdgeStyleParameters.EdgeColor.B,
EdgeStyleParameters.EdgeColor.A
};
}
/// Color map
union AllColorMapDescriptors
{
XrPassthroughColorMapMonoToRgbaFB rgba;
XrPassthroughColorMapMonoToMonoFB mono;
XrPassthroughBrightnessContrastSaturationFB bcs;
XrPassthroughColorMapLutMETA lut;
XrPassthroughColorMapInterpolatedLutMETA interpLut;
};
AllColorMapDescriptors colorMap;
if (PassthroughExtension.Pin()->GetSettings()->bExtColorLutAvailable && EdgeStyleParameters.bEnableColorMap)
{
void* colorMapDataDestination = nullptr;
unsigned int expectedColorMapDataSize = 0;
switch (EdgeStyleParameters.ColorMapType)
{
case ColorMapType_None:
break;
case ColorMapType_GrayscaleToColor:
colorMap.rgba = { XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_RGBA_FB };
expectedColorMapDataSize = sizeof(colorMap.rgba.textureColorMap);
colorMapDataDestination = colorMap.rgba.textureColorMap;
Style.next = &colorMap.rgba;
break;
case ColorMapType_Grayscale:
colorMap.mono = { XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_MONO_FB };
expectedColorMapDataSize = sizeof(colorMap.mono.textureColorMap);
colorMapDataDestination = colorMap.mono.textureColorMap;
Style.next = &colorMap.mono;
break;
case ColorMapType_ColorAdjustment:
colorMap.bcs = { XR_TYPE_PASSTHROUGH_BRIGHTNESS_CONTRAST_SATURATION_FB };
expectedColorMapDataSize = 3 * sizeof(float);
colorMapDataDestination = &colorMap.bcs.brightness;
Style.next = &colorMap.bcs;
break;
case ColorMapType_ColorLut:
colorMap.lut = { XR_TYPE_PASSTHROUGH_COLOR_MAP_LUT_META };
colorMap.lut.colorLut = reinterpret_cast<const XrPassthroughColorLutMETA&>(EdgeStyleParameters.ColorLutDesc.ColorLuts[0]);
colorMap.lut.weight = EdgeStyleParameters.ColorLutDesc.Weight;
Style.next = &colorMap.lut;
break;
case ColorMapType_ColorLut_Interpolated:
colorMap.interpLut = { XR_TYPE_PASSTHROUGH_COLOR_MAP_INTERPOLATED_LUT_META };
colorMap.interpLut.sourceColorLut = reinterpret_cast<const XrPassthroughColorLutMETA&>(EdgeStyleParameters.ColorLutDesc.ColorLuts[0]);
colorMap.interpLut.targetColorLut = reinterpret_cast<const XrPassthroughColorLutMETA&>(EdgeStyleParameters.ColorLutDesc.ColorLuts[1]);
colorMap.interpLut.weight = EdgeStyleParameters.ColorLutDesc.Weight;
Style.next = &colorMap.lut;
break;
default:
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Passthrough style has unexpected color map type: %i"), EdgeStyleParameters.ColorMapType);
return;
}
// Validate color map data size and copy it over
if (colorMapDataDestination != nullptr)
{
if (EdgeStyleParameters.ColorMapData.Num() != expectedColorMapDataSize)
{
UE_LOG(LogOculusXRPassthrough, Error,
TEXT("Passthrough color map size for type %i is expected to be %i instead of %i"),
EdgeStyleParameters.ColorMapType,
expectedColorMapDataSize,
EdgeStyleParameters.ColorMapData.Num());
return;
}
uint8* ColorMapData = (uint8*)EdgeStyleParameters.ColorMapData.GetData();
memcpy(colorMapDataDestination, ColorMapData, expectedColorMapDataSize);
}
}
XrResult Result = xrPassthroughLayerSetStyleFB(XrPassthroughLayer, &Style);
if (!XR_SUCCEEDED(Result))
{
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed setting passthrough style, error : %i"), Result);
return;
}
}
static FMatrix TransformToPassthroughSpace(FTransform Transform, float WorldToMetersScale, FTransform TrackingToWorld)
{
const FVector WorldToMetersScaleInv = FVector(WorldToMetersScale).Reciprocal();
FTransform TransformWorld = Transform * TrackingToWorld.Inverse();
TransformWorld.MultiplyScale3D(WorldToMetersScaleInv);
TransformWorld.ScaleTranslation(WorldToMetersScaleInv);
const FMatrix TransformWorldScaled = TransformWorld.ToMatrixWithScale();
const FMatrix SwapAxisMatrix(
FPlane(0.0f, 0.0f, -1.0f, 0.0f),
FPlane(1.0f, 0.0f, 0.0f, 0.0f),
FPlane(0.0f, 1.0f, 0.0f, 0.0f),
FPlane(0.0f, 0.0f, 0.0f, 1.0f));
return TransformWorldScaled * SwapAxisMatrix;
}
void FPassthroughLayer::UpdatePassthrough_RenderThread(FRHICommandListImmediate& RHICmdList, XrSpace Space, XrTime Time, float WorldToMetersScale, FTransform TrackingToWorld)
{
check(IsInRenderingThread());
if (LayerDesc.HasShape<FReconstructedLayer>())
{
const FReconstructedLayer& ReconstructedLayerProps = LayerDesc.GetShape<FReconstructedLayer>();
UpdatePassthroughStyle_RenderThread(ReconstructedLayerProps.EdgeStyleParameters);
}
else if (LayerDesc.HasShape<FUserDefinedLayer>())
{
const FUserDefinedLayer& UserDefinedLayerProps = LayerDesc.GetShape<FUserDefinedLayer>();
UpdatePassthroughStyle_RenderThread(UserDefinedLayerProps.EdgeStyleParameters);
}
if (LayerDesc.HasShape<FUserDefinedLayer>())
{
const FUserDefinedLayer& UserDefinedLayerProps = LayerDesc.GetShape<FUserDefinedLayer>();
const TArray<FUserDefinedGeometryDesc>& UserGeometryList = UserDefinedLayerProps.UserGeometryList;
TSet<FString> UsedSet;
for (const FUserDefinedGeometryDesc& GeometryDesc : UserGeometryList)
{
const FString MeshName = GeometryDesc.MeshName;
UsedSet.Add(MeshName);
FPassthroughMesh* LayerPassthroughMesh = UserDefinedGeometryMap->Find(MeshName);
if (!LayerPassthroughMesh)
{
OculusXRHMD::FOculusPassthroughMeshRef GeomPassthroughMesh = GeometryDesc.PassthroughMesh;
if (GeomPassthroughMesh)
{
const FMatrix Transform = TransformToPassthroughSpace(GeometryDesc.Transform, WorldToMetersScale, TrackingToWorld);
XrTriangleMeshFB MeshHandle = 0;
XrGeometryInstanceFB InstanceHandle = 0;
AddPassthroughMesh_RenderThread(GeomPassthroughMesh->GetVertices(), GeomPassthroughMesh->GetTriangles(), Transform, Space, MeshHandle, InstanceHandle);
UserDefinedGeometryMap->Add(MeshName, FPassthroughMesh(MeshHandle, InstanceHandle, GeometryDesc.Transform));
}
}
else
{
const FMatrix Transform = TransformToPassthroughSpace(GeometryDesc.Transform, WorldToMetersScale, TrackingToWorld);
UpdatePassthroughMeshTransform_RenderThread(LayerPassthroughMesh->InstanceHandle, Transform, Space, Time);
LayerPassthroughMesh->LastTransform = GeometryDesc.Transform;
}
}
// find meshes that no longer exist
TArray<FString> ItemsToRemove;
for (auto& Entry : *UserDefinedGeometryMap)
{
if (!UsedSet.Contains(Entry.Key))
{
ItemsToRemove.Add(Entry.Key);
}
}
for (FString Entry : ItemsToRemove)
{
FPassthroughMesh* PassthroughMesh = UserDefinedGeometryMap->Find(Entry);
if (PassthroughMesh)
{
const XrTriangleMeshFB MeshHandle = PassthroughMesh->MeshHandle;
const XrGeometryInstanceFB InstanceHandle = PassthroughMesh->InstanceHandle;
RemovePassthroughMesh_RenderThread(MeshHandle, InstanceHandle);
}
else
{
UE_LOG(LogOculusXRPassthrough, Error, TEXT("PassthroughMesh: %s doesn't exist."), *Entry);
return;
}
UserDefinedGeometryMap->Remove(Entry);
}
}
}
XrCompositionLayerBaseHeaderType* FPassthroughLayer::GetXrCompositionLayerHeader()
{
OculusXRHMD::CheckInRHIThread();
if (XrPassthroughLayer != nullptr)
{
XrCompositionLayerPassthroughFB& CompositionLayer = XrCompositionLayerHeader;
memset(&CompositionLayer, 0, sizeof(CompositionLayer));
CompositionLayer.type = XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB;
CompositionLayer.layerHandle = XrPassthroughLayer;
CompositionLayer.flags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
CompositionLayer.space = XR_NULL_HANDLE;
return reinterpret_cast<XrCompositionLayerBaseHeaderType*>(&CompositionLayer);
}
return nullptr;
}
bool FPassthroughLayer::IsBackgroundLayer() const
{
return (LayerDesc.HasShape<FReconstructedLayer>() && (LayerDesc.GetShape<FReconstructedLayer>().PassthroughLayerOrder == PassthroughLayerOrder_Underlay))
|| (LayerDesc.HasShape<FUserDefinedLayer>() && (LayerDesc.GetShape<FUserDefinedLayer>().PassthroughLayerOrder == PassthroughLayerOrder_Underlay));
}
bool FPassthroughLayer::IsOverlayLayer() const
{
return (LayerDesc.HasShape<FReconstructedLayer>() && (LayerDesc.GetShape<FReconstructedLayer>().PassthroughLayerOrder == PassthroughLayerOrder_Overlay))
|| (LayerDesc.HasShape<FUserDefinedLayer>() && (LayerDesc.GetShape<FUserDefinedLayer>().PassthroughLayerOrder == PassthroughLayerOrder_Overlay));
}
bool FPassthroughLayer::PassthroughSupportsDepth() const
{
return ((LayerDesc.Flags & IStereoLayers::LAYER_FLAG_SUPPORT_DEPTH) != 0) && LayerDesc.HasShape<FUserDefinedLayer>();
}
// Code taken from OVRPlugin (InsightMrManager.cpp)
static bool DecomposeTransformMatrix(FMatrix Transform, XrPosef& OutPose, XrVector3f& OutScale)
{
FTransform outTransform = FTransform(Transform);
FVector3f scale = FVector3f(outTransform.GetScale3D());
FQuat4f rotation = FQuat4f(outTransform.GetRotation());
FVector3f position = FVector3f(outTransform.GetLocation());
if (scale.X == 0 || scale.Y == 0 || scale.Z == 0)
{
return false;
}
OutScale = XrVector3f{ scale.X, scale.Y, scale.Z };
OutPose = XrPosef{ XrQuaternionf{ rotation.X, rotation.Y, rotation.Z, rotation.W }, XrVector3f{ position.X, position.Y, position.Z } };
return true;
}
void FPassthroughLayer::AddPassthroughMesh_RenderThread(const TArray<FVector>& Vertices, const TArray<int32>& Triangles, FMatrix Transformation, XrSpace Space, XrTriangleMeshFB& OutMeshHandle, XrGeometryInstanceFB& OutInstanceHandle)
{
OculusXRHMD::CheckInRenderThread();
XrTriangleMeshFB MeshHandle = 0;
XrGeometryInstanceFB InstanceHandle = 0;
// Explicit conversion is needed since FVector contains double elements.
// Converting Vertices.Data() to float* causes issues when memory is parsed.
TArray<XrVector3f> VertexData;
VertexData.SetNumUninitialized(Vertices.Num());
size_t i = 0;
for (const FVector& vertex : Vertices)
{
VertexData[i++] = { (float)vertex.X, (float)vertex.Y, (float)vertex.Z };
}
TArray<uint32_t> TriangleData;
TriangleData.SetNumUninitialized(Triangles.Num());
i = 0;
for (const int32& tri : Triangles)
{
TriangleData[i++] = (uint32_t)tri;
}
XrTriangleMeshCreateInfoFB TriangleMeshInfo = { XR_TYPE_TRIANGLE_MESH_CREATE_INFO_FB };
TriangleMeshInfo.flags = 0; // not mutable
TriangleMeshInfo.triangleCount = Triangles.Num() / 3;
TriangleMeshInfo.indexBuffer = TriangleData.GetData();
TriangleMeshInfo.vertexCount = Vertices.Num();
TriangleMeshInfo.vertexBuffer = VertexData.GetData();
TriangleMeshInfo.windingOrder = XR_WINDING_ORDER_UNKNOWN_FB;
if (XR_FAILED(xrCreateTriangleMeshFB.GetValue()(Session, &TriangleMeshInfo, &MeshHandle)))
{
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed creating passthrough mesh surface."));
return;
}
XrGeometryInstanceCreateInfoFB createInfo = { XR_TYPE_GEOMETRY_INSTANCE_CREATE_INFO_FB };
bool result = DecomposeTransformMatrix(Transformation, createInfo.pose, createInfo.scale);
if (!result)
{
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed decomposing the transform matrix."));
return;
}
createInfo.layer = XrPassthroughLayer;
createInfo.mesh = MeshHandle;
createInfo.baseSpace = Space;
if (XR_FAILED(xrCreateGeometryInstanceFB(Session, &createInfo, &InstanceHandle)))
{
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed adding passthrough mesh surface to scene."));
return;
}
OutMeshHandle = MeshHandle;
OutInstanceHandle = InstanceHandle;
}
void FPassthroughLayer::UpdatePassthroughMeshTransform_RenderThread(XrGeometryInstanceFB InstanceHandle, FMatrix Transformation, XrSpace Space, XrTime Time)
{
OculusXRHMD::CheckInRenderThread();
XrGeometryInstanceTransformFB UpdateInfo = { XR_TYPE_GEOMETRY_INSTANCE_TRANSFORM_FB };
bool result = DecomposeTransformMatrix(Transformation, UpdateInfo.pose, UpdateInfo.scale);
if (!result)
{
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed decomposing the transform matrix."));
return;
}
UpdateInfo.baseSpace = Space;
UpdateInfo.time = Time;
if (XR_FAILED(xrGeometryInstanceSetTransformFB(InstanceHandle, &UpdateInfo)))
{
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed updating passthrough mesh surface transform."));
return;
}
}
void FPassthroughLayer::RemovePassthroughMesh_RenderThread(XrTriangleMeshFB MeshHandle, XrGeometryInstanceFB InstanceHandle)
{
OculusXRHMD::CheckInRenderThread();
if (XR_FAILED(xrDestroyGeometryInstanceFB(InstanceHandle)))
{
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed removing passthrough surface from scene."));
return;
}
if (XR_FAILED(xrDestroyTriangleMeshFB.GetValue()(MeshHandle)))
{
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed destroying passthrough surface mesh."));
return;
}
}
void FPassthroughLayer::ClearPassthroughPokeActors()
{
if (PassthroughPokeActorMap)
{
UWorld* World = GetWorld();
if (!World)
{
UE_LOG(LogOculusXRPassthrough, Warning, TEXT("Couldn't retrieve World. Passthrough Pokeahole actors will not be destroyed."));
return;
}
for (auto& Entry : *PassthroughPokeActorMap)
{
// Check if actor is still valid. In some specific cases the actor might be destroyed before we clean the PokeActorMap
// (e.g. when loading to a new level)
if (Entry.Value.PokeAHoleActor.IsValid())
{
World->DestroyActor(Entry.Value.PokeAHoleActor.Get());
}
}
PassthroughPokeActorMap.Reset();
}
}
} // namespace XRPassthrough