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,82 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRRoomLayoutManagerComponent.h"
#include "OculusXRScene.h"
#include "OculusXRSceneModule.h"
#include "OculusXRSceneDelegates.h"
#include "OculusXRSceneFunctionLibrary.h"
#include "ProceduralMeshComponent.h"
UOculusXRRoomLayoutManagerComponent::UOculusXRRoomLayoutManagerComponent(const FObjectInitializer& ObjectInitializer)
{
bWantsInitializeComponent = true; // so that InitializeComponent() gets called
}
void UOculusXRRoomLayoutManagerComponent::OnRegister()
{
Super::OnRegister();
FOculusXRSceneEventDelegates::OculusSceneCaptureComplete.AddUObject(this, &UOculusXRRoomLayoutManagerComponent::OculusRoomLayoutSceneCaptureComplete_Handler);
}
void UOculusXRRoomLayoutManagerComponent::OnUnregister()
{
Super::OnUnregister();
FOculusXRSceneEventDelegates::OculusSceneCaptureComplete.RemoveAll(this);
}
void UOculusXRRoomLayoutManagerComponent::InitializeComponent()
{
Super::InitializeComponent();
}
void UOculusXRRoomLayoutManagerComponent::UninitializeComponent()
{
Super::UninitializeComponent();
}
bool UOculusXRRoomLayoutManagerComponent::LaunchCaptureFlow()
{
UE_LOG(LogOculusXRScene, Verbose, TEXT("Launch capture flow -- UOculusXRRoomLayoutManagerComponent"));
uint64 OutRequest = 0;
auto result = OculusXRScene::FOculusXRScene::RequestSceneCapture(OutRequest);
bool isSuccess = UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(result);
if (isSuccess)
{
EntityRequestList.Add(OutRequest);
}
UE_LOG(LogOculusXRScene, Verbose, TEXT("Launch capture flow -- RequestSceneCapture -- %d"), result);
return isSuccess;
}
bool UOculusXRRoomLayoutManagerComponent::GetRoomLayout(FOculusXRUInt64 Space, FOculusXRRoomLayout& RoomLayoutOut, int32 MaxWallsCapacity)
{
return UOculusXRSceneFunctionLibrary::GetRoomLayout(Space, RoomLayoutOut, MaxWallsCapacity);
}
bool UOculusXRRoomLayoutManagerComponent::LoadTriangleMesh(FOculusXRUInt64 Space, UProceduralMeshComponent* Mesh, bool CreateCollision) const
{
ensure(Mesh);
TArray<FVector> Vertices;
TArray<int32> Triangles;
auto result = OculusXRScene::FOculusXRScene::GetTriangleMesh(Space, Vertices, Triangles);
bool isSuccess = UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(result);
if (!isSuccess)
{
return false;
}
// Mesh->bUseAsyncCooking = true;
TArray<FVector> EmptyNormals;
TArray<FVector2D> EmptyUV;
TArray<FColor> EmptyVertexColors;
TArray<FProcMeshTangent> EmptyTangents;
Mesh->CreateMeshSection(0, Vertices, Triangles, EmptyNormals, EmptyUV, EmptyVertexColors, EmptyTangents, CreateCollision);
return true;
}

View File

@@ -0,0 +1,85 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRScene.h"
#include "OculusXRSceneModule.h"
#include "OculusXRHMDPrivate.h"
#include "OculusXRHMD.h"
#include "OculusXRPluginWrapper.h"
#include "OculusXRSceneFunctionsOVR.h"
#include "OculusXRSceneFunctionsOpenXR.h"
#define LOCTEXT_NAMESPACE "OculusXRScene"
namespace OculusXRScene
{
EOculusXRAnchorResult::Type FOculusXRScene::GetScenePlane(uint64 AnchorHandle, FVector& OutPos, FVector& OutSize)
{
return GetOculusXRSceneFunctionsImpl()->GetScenePlane(AnchorHandle, OutPos, OutSize);
}
EOculusXRAnchorResult::Type FOculusXRScene::GetSceneVolume(uint64 AnchorHandle, FVector& OutPos, FVector& OutSize)
{
return GetOculusXRSceneFunctionsImpl()->GetSceneVolume(AnchorHandle, OutPos, OutSize);
}
EOculusXRAnchorResult::Type FOculusXRScene::GetSemanticClassification(uint64 AnchorHandle, TArray<FString>& OutSemanticClassifications)
{
return GetOculusXRSceneFunctionsImpl()->GetSemanticClassification(AnchorHandle, OutSemanticClassifications);
}
EOculusXRAnchorResult::Type FOculusXRScene::GetBoundary2D(uint64 AnchorHandle, TArray<FVector2f>& OutVertices)
{
return GetOculusXRSceneFunctionsImpl()->GetBoundary2D(AnchorHandle, OutVertices);
}
EOculusXRAnchorResult::Type FOculusXRScene::RequestSceneCapture(uint64& OutRequestID)
{
return GetOculusXRSceneFunctionsImpl()->RequestSceneCapture(OutRequestID);
}
EOculusXRAnchorResult::Type FOculusXRScene::GetRoomLayout(uint64 AnchorHandle, const uint32 MaxWallsCapacity, FOculusXRUUID& OutCeilingUuid, FOculusXRUUID& OutFloorUuid, TArray<FOculusXRUUID>& OutWallsUuid)
{
return GetOculusXRSceneFunctionsImpl()->GetRoomLayout(AnchorHandle, MaxWallsCapacity, OutCeilingUuid, OutFloorUuid, OutWallsUuid);
}
EOculusXRAnchorResult::Type FOculusXRScene::GetTriangleMesh(uint64 AnchorHandle, TArray<FVector>& Vertices, TArray<int32>& Triangles)
{
return GetOculusXRSceneFunctionsImpl()->GetTriangleMesh(AnchorHandle, Vertices, Triangles);
}
// Requests to change the current boundary visibility
EOculusXRAnchorResult::Type FOculusXRScene::RequestBoundaryVisibility(EOculusXRBoundaryVisibility NewVisibilityRequest)
{
return GetOculusXRSceneFunctionsImpl()->RequestBoundaryVisibility(NewVisibilityRequest);
}
EOculusXRAnchorResult::Type FOculusXRScene::GetBoundaryVisibility(EOculusXRBoundaryVisibility& OutVisibility)
{
return GetOculusXRSceneFunctionsImpl()->GetBoundaryVisibility(OutVisibility);
}
TSharedPtr<IOculusXRSceneFunctions> FOculusXRScene::SceneFunctionsImpl = nullptr;
TSharedPtr<IOculusXRSceneFunctions> FOculusXRScene::GetOculusXRSceneFunctionsImpl()
{
if (SceneFunctionsImpl == nullptr)
{
const FName SystemName(TEXT("OpenXR"));
const bool IsOpenXR = GEngine->XRSystem.IsValid() && (GEngine->XRSystem->GetSystemName() == SystemName);
if (OculusXRHMD::FOculusXRHMD::GetOculusXRHMD() != nullptr)
{
SceneFunctionsImpl = MakeShared<FOculusXRSceneFunctionsOVR>();
}
else if (IsOpenXR)
{
SceneFunctionsImpl = MakeShared<FOculusXRSceneFunctionsOpenXR>();
}
}
check(SceneFunctionsImpl);
return SceneFunctionsImpl;
}
} // namespace OculusXRScene
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,784 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#include "OculusXRSceneActor.h"
#include "OculusXRSceneModule.h"
#include "OculusXRHMDModule.h"
#include "OculusXRHMD.h"
#include "OculusXRAnchorManager.h"
#include "OculusXRAnchorTypes.h"
#include "OculusXRAnchorBPFunctionLibrary.h"
#include "OculusXRScene.h"
#include "OculusXRSceneDelegates.h"
#include "OculusXRDelegates.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/AssetManager.h"
#include "Engine/StaticMesh.h"
#include "Engine/StaticMeshActor.h"
#include "Engine/World.h"
#include "GameFramework/WorldSettings.h"
#include "ProceduralMeshComponent.h"
#include "OculusXRSceneGlobalMeshComponent.h"
#define LOCTEXT_NAMESPACE "OculusXRSceneActor"
//////////////////////////////////////////////////////////////////////////
// ASceneActor
AOculusXRSceneActor::AOculusXRSceneActor(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
ResetStates();
// Create required components
RoomLayoutManagerComponent = CreateDefaultSubobject<UOculusXRRoomLayoutManagerComponent>(TEXT("OculusXRRoomLayoutManagerComponent"));
// Following are the semantic labels we want to support default properties for. User can always add new ones through the properties panel if needed.
const FString default2DSemanticClassifications[] = {
TEXT("WALL_FACE"),
TEXT("CEILING"),
TEXT("FLOOR"),
TEXT("COUCH"),
TEXT("TABLE"),
TEXT("DOOR_FRAME"),
TEXT("WINDOW_FRAME"),
TEXT("WALL_ART"),
TEXT("INVISIBLE_WALL_FACE"),
TEXT("OTHER")
};
const FString default3DSemanticClassifications[] = {
TEXT("COUCH"),
TEXT("TABLE"),
TEXT("SCREEN"),
TEXT("BED"),
TEXT("LAMP"),
TEXT("PLANT"),
TEXT("STORAGE"),
TEXT("OTHER")
};
FOculusXRSpawnedSceneAnchorProperties spawnedAnchorProps;
spawnedAnchorProps.ActorComponent = nullptr;
spawnedAnchorProps.StaticMesh = nullptr;
// Setup initial scene plane and volume properties
for (auto& semanticLabel2D : default2DSemanticClassifications)
{
FOculusXRSpawnedSceneAnchorProperties& props = ScenePlaneSpawnedSceneAnchorProperties.Add(semanticLabel2D, spawnedAnchorProps);
props.ForceParallelToFloor = (semanticLabel2D != "WALL_FACE");
}
for (auto& semanticLabel3D : default3DSemanticClassifications)
{
FOculusXRSpawnedSceneAnchorProperties& props = SceneVolumeSpawnedSceneAnchorProperties.Add(semanticLabel3D, spawnedAnchorProps);
props.ForceParallelToFloor = true;
}
}
void AOculusXRSceneActor::ResetStates()
{
bCaptureFlowWasLaunched = false;
ClearScene();
}
void AOculusXRSceneActor::BeginPlay()
{
Super::BeginPlay();
// Create a scene component as root so we can attach spawned actors to it
USceneComponent* rootSceneComponent = NewObject<USceneComponent>(this, USceneComponent::StaticClass());
rootSceneComponent->SetMobility(EComponentMobility::Static);
rootSceneComponent->RegisterComponent();
SetRootComponent(rootSceneComponent);
SceneGlobalMeshComponent = FindComponentByClass<UOculusXRSceneGlobalMeshComponent>();
// Register delegates
RoomLayoutManagerComponent->OculusXRRoomLayoutSceneCaptureCompleteNative.AddUObject(this, &AOculusXRSceneActor::SceneCaptureComplete_Handler);
// Make an initial request to query for the room layout if bPopulateSceneOnBeginPlay was set to true
if (bPopulateSceneOnBeginPlay)
{
PopulateScene();
}
}
void AOculusXRSceneActor::EndPlay(EEndPlayReason::Type Reason)
{
// Unregister delegates
RoomLayoutManagerComponent->OculusXRRoomLayoutSceneCaptureCompleteNative.RemoveAll(this);
// Calling ResetStates will reset member variables to their default values (including the request IDs).
ResetStates();
Super::EndPlay(Reason);
}
void AOculusXRSceneActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
bool AOculusXRSceneActor::IsValidUuid(const FOculusXRUUID& Uuid)
{
return Uuid.UUIDBytes != nullptr;
}
void AOculusXRSceneActor::LaunchCaptureFlow()
{
UE_LOG(LogOculusXRScene, Verbose, TEXT("Launch capture flow"));
if (RoomLayoutManagerComponent)
{
UE_LOG(LogOculusXRScene, Verbose, TEXT("Launch capture flow -- RoomLayoutManagerComponent"));
const bool bResult = RoomLayoutManagerComponent->LaunchCaptureFlow();
if (!bResult)
{
UE_LOG(LogOculusXRScene, Error, TEXT("LaunchCaptureFlow() failed!"));
}
}
}
void AOculusXRSceneActor::LaunchCaptureFlowIfNeeded()
{
#if WITH_EDITOR
UE_LOG(LogOculusXRScene, Display, TEXT("Scene Capture does not work over Link. Please capture a scene with the HMD in standalone mode, then access the scene model over Link."));
#else
// Depending on LauchCaptureFlowWhenMissingScene, we might not want to launch Capture Flow
if (LauchCaptureFlowWhenMissingScene != EOculusXRLaunchCaptureFlowWhenMissingScene::NEVER)
{
if (LauchCaptureFlowWhenMissingScene == EOculusXRLaunchCaptureFlowWhenMissingScene::ALWAYS || (!bCaptureFlowWasLaunched && LauchCaptureFlowWhenMissingScene == EOculusXRLaunchCaptureFlowWhenMissingScene::ONCE))
{
LaunchCaptureFlow();
}
}
#endif
}
AActor* AOculusXRSceneActor::SpawnActorWithSceneComponent(const FOculusXRUInt64& Space, const FOculusXRUInt64& RoomSpaceID, const TArray<FString>& SemanticClassifications, UClass* sceneAnchorComponentInstanceClass)
{
FActorSpawnParameters actorSpawnParams;
actorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
AActor* Anchor = GetWorld()->SpawnActor<AActor>(AActor::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator, actorSpawnParams);
USceneComponent* rootComponent = NewObject<USceneComponent>(Anchor, USceneComponent::StaticClass());
rootComponent->SetMobility(EComponentMobility::Movable);
rootComponent->RegisterComponent();
Anchor->SetRootComponent(rootComponent);
rootComponent->SetWorldLocation(FVector::ZeroVector);
Anchor->AttachToActor(this, FAttachmentTransformRules::KeepRelativeTransform);
#if WITH_EDITOR
if (SemanticClassifications.Num() > 0)
{
Anchor->SetActorLabel(FString::Join(SemanticClassifications, TEXT("-")), false);
}
#endif
UOculusXRSceneAnchorComponent* sceneAnchorComponent = NewObject<UOculusXRSceneAnchorComponent>(Anchor, sceneAnchorComponentInstanceClass);
sceneAnchorComponent->RegisterComponent();
sceneAnchorComponent->SetHandle(Space);
sceneAnchorComponent->SemanticClassifications = SemanticClassifications;
sceneAnchorComponent->RoomSpaceID = RoomSpaceID;
EOculusXRAnchorResult::Type Result;
OculusXRAnchors::FOculusXRAnchors::SetAnchorComponentStatus(sceneAnchorComponent, EOculusXRSpaceComponentType::Locatable, true, 0.0f, FOculusXRAnchorSetComponentStatusDelegate(), Result);
return Anchor;
}
AActor* AOculusXRSceneActor::SpawnOrUpdateSceneAnchor(AActor* Anchor, const FOculusXRUInt64& Space, const FOculusXRUInt64& RoomSpaceID, const FVector& BoundedPos, const FVector& BoundedSize, const TArray<FString>& SemanticClassifications, const EOculusXRSpaceComponentType AnchorComponentType)
{
if (Space.Value == 0)
{
UE_LOG(LogOculusXRScene, Error, TEXT("AOculusXRSceneActor::SpawnOrUpdateSceneAnchor Invalid Space handle."));
return Anchor;
}
if (!(AnchorComponentType == EOculusXRSpaceComponentType::ScenePlane || AnchorComponentType == EOculusXRSpaceComponentType::SceneVolume))
{
UE_LOG(LogOculusXRScene, Error, TEXT("AOculusXRSceneActor::SpawnOrUpdateSceneAnchor Anchor doesn't have ScenePlane or SceneVolume component active."));
return Anchor;
}
if (0 == SemanticClassifications.Num())
{
UE_LOG(LogOculusXRScene, Error, TEXT("AOculusXRSceneActor::SpawnOrUpdateSceneAnchor No semantic classification found."));
return Anchor;
}
FOculusXRSpawnedSceneAnchorProperties* foundProperties = (AnchorComponentType == EOculusXRSpaceComponentType::ScenePlane) ? ScenePlaneSpawnedSceneAnchorProperties.Find(SemanticClassifications[0]) : SceneVolumeSpawnedSceneAnchorProperties.Find(SemanticClassifications[0]);
if (!foundProperties)
{
UE_LOG(LogOculusXRScene, Warning, TEXT("AOculusXRSceneActor::SpawnOrUpdateSceneAnchor Scene object has an unknown semantic label. Will not be spawned."));
return Anchor;
}
TSoftClassPtr<UOculusXRSceneAnchorComponent>* sceneAnchorComponentClassPtrRef = &foundProperties->ActorComponent;
TSoftObjectPtr<UStaticMesh>* staticMeshObjPtrRef = &foundProperties->StaticMesh;
UClass* sceneAnchorComponentInstanceClass = sceneAnchorComponentClassPtrRef->LoadSynchronous();
if (!sceneAnchorComponentInstanceClass)
{
UE_LOG(LogOculusXRScene, Error, TEXT("AOculusXRSceneActor::SpawnOrUpdateSceneAnchor Scene anchor component class is invalid! Cannot spawn actor to populate the scene."));
return Anchor;
}
if (!Anchor)
{
Anchor = SpawnActorWithSceneComponent(Space, RoomSpaceID, SemanticClassifications, sceneAnchorComponentInstanceClass);
}
if (staticMeshObjPtrRef && staticMeshObjPtrRef->IsPending())
{
staticMeshObjPtrRef->LoadSynchronous();
}
UStaticMesh* refStaticMesh = staticMeshObjPtrRef ? staticMeshObjPtrRef->Get() : nullptr;
if (refStaticMesh == nullptr)
{
UE_LOG(LogOculusXRScene, Warning, TEXT("AOculusXRSceneActor::SpawnOrUpdateSceneAnchor Spawn scene anchor mesh is invalid for %s!"), *SemanticClassifications[0]);
return Anchor;
}
UStaticMeshComponent* staticMeshComponent = NewObject<UStaticMeshComponent>(Anchor, UStaticMeshComponent::StaticClass());
staticMeshComponent->RegisterComponent();
staticMeshComponent->SetStaticMesh(refStaticMesh);
staticMeshComponent->AttachToComponent(Anchor->GetRootComponent(), FAttachmentTransformRules::KeepWorldTransform);
const float worldToMeters = GetWorld()->GetWorldSettings()->WorldToMeters;
FVector offset(0.0f, BoundedSize.Y / 2.0f, BoundedSize.Z / 2.0f);
staticMeshComponent->SetRelativeLocation(foundProperties->AddOffset + ((BoundedPos + offset) * worldToMeters), false, nullptr, ETeleportType::ResetPhysics);
// Setup scale based on bounded size and the actual size of the mesh
UStaticMesh* staticMesh = staticMeshComponent->GetStaticMesh();
FBoxSphereBounds staticMeshBounds;
staticMeshBounds.BoxExtent = FVector{ 1.f, 1.f, 1.f };
if (staticMesh)
{
staticMeshBounds = staticMesh->GetBounds();
}
staticMeshComponent->SetRelativeScale3D(FVector(
(BoundedSize.X < UE_SMALL_NUMBER) ? 1 : (BoundedSize.X / (staticMeshBounds.BoxExtent.X * 2.f)) * worldToMeters,
(BoundedSize.Y < UE_SMALL_NUMBER) ? 1 : (BoundedSize.Y / (staticMeshBounds.BoxExtent.Y * 2.f)) * worldToMeters,
(BoundedSize.Z < UE_SMALL_NUMBER) ? 1 : (BoundedSize.Z / (staticMeshBounds.BoxExtent.Z * 2.f)) * worldToMeters));
return Anchor;
}
bool AOculusXRSceneActor::IsScenePopulated()
{
if (!RootComponent)
{
return false;
}
return RootComponent->GetNumChildrenComponents() > 0;
}
bool AOculusXRSceneActor::IsRoomLayoutValid()
{
return true;
}
void AOculusXRSceneActor::PopulateScene()
{
if (!RootComponent)
{
return;
}
const EOculusXRAnchorResult::Type result = QueryAllRooms();
if (!UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(result))
{
UE_LOG(LogOculusXRScene, Error, TEXT("PopulateScene Failed to query available rooms"));
}
}
void AOculusXRSceneActor::ClearScene()
{
if (!RootComponent)
return;
TArray<USceneComponent*> childrenComponents = RootComponent->GetAttachChildren();
for (USceneComponent* SceneComponent : childrenComponents)
{
Cast<AActor>(SceneComponent->GetOuter())->Destroy();
}
bRoomLayoutIsValid = false;
bFoundCapturedScene = false;
}
void AOculusXRSceneActor::SetVisibilityToAllSceneAnchors(const bool bIsVisible)
{
if (!RootComponent)
return;
TArray<USceneComponent*> childrenComponents = RootComponent->GetAttachChildren();
for (USceneComponent* sceneComponent : childrenComponents)
{
sceneComponent->SetVisibility(bIsVisible, true);
}
}
void AOculusXRSceneActor::SetVisibilityToSceneAnchorsBySemanticLabel(const FString SemanticLabel, const bool bIsVisible)
{
FString label = SemanticLabel;
if (SemanticLabel == TEXT("DESK"))
{
label = TEXT("TABLE");
UE_LOG(LogOculusXRScene, Warning, TEXT("XR Scene Actor semantic lable 'DESK' is deprecated, use 'TABLE' instead."));
}
if (!RootComponent)
return;
TArray<USceneComponent*> childrenComponents = RootComponent->GetAttachChildren();
for (USceneComponent* sceneComponent : childrenComponents)
{
UObject* outerObject = sceneComponent->GetOuter();
if (!outerObject)
{
continue;
}
AActor* outerActor = Cast<AActor>(outerObject);
if (!outerActor)
{
continue;
}
UActorComponent* sceneAnchorComponent = outerActor->GetComponentByClass(UOculusXRSceneAnchorComponent::StaticClass());
if (!sceneAnchorComponent)
{
continue;
}
if (Cast<UOculusXRSceneAnchorComponent>(sceneAnchorComponent)->SemanticClassifications.Contains(label))
{
sceneComponent->SetVisibility(bIsVisible, true);
}
}
}
TArray<AActor*> AOculusXRSceneActor::GetActorsBySemanticLabel(const FString SemanticLabel)
{
FString label = SemanticLabel;
if (SemanticLabel == TEXT("DESK"))
{
label = TEXT("TABLE");
UE_LOG(LogOculusXRScene, Warning, TEXT("XR Scene Actor semantic lable 'DESK' is deprecated, use 'TABLE' instead."));
}
TArray<AActor*> actors;
if (!RootComponent)
return actors;
TArray<USceneComponent*> childrenComponents = RootComponent->GetAttachChildren();
for (USceneComponent* sceneComponent : childrenComponents)
{
UObject* outerObject = sceneComponent->GetOuter();
if (!outerObject)
{
continue;
}
AActor* outerActor = Cast<AActor>(outerObject);
if (!outerActor)
{
continue;
}
UActorComponent* sceneAnchorComponent = outerActor->GetComponentByClass(UOculusXRSceneAnchorComponent::StaticClass());
if (!sceneAnchorComponent)
{
continue;
}
if (Cast<UOculusXRSceneAnchorComponent>(sceneAnchorComponent)->SemanticClassifications.Contains(label))
{
actors.Add(outerActor);
}
}
return actors;
}
TArray<FOculusXRRoomLayout> AOculusXRSceneActor::GetRoomLayouts() const
{
TArray<FOculusXRRoomLayout> layouts;
RoomLayouts.GenerateValueArray(layouts);
return layouts;
}
EOculusXRAnchorResult::Type AOculusXRSceneActor::QueryAllRooms()
{
EOculusXRAnchorResult::Type anchorQueryResult;
auto componentFilter = NewObject<UOculusXRSpaceDiscoveryComponentsFilter>(this);
componentFilter->ComponentType = EOculusXRSpaceComponentType::RoomLayout;
FOculusXRSpaceDiscoveryInfo discoveryInfo;
discoveryInfo.Filters.Add(componentFilter);
OculusXRAnchors::FOculusXRAnchors::DiscoverAnchors(discoveryInfo,
FOculusXRDiscoverAnchorsResultsDelegate::CreateUObject(this, &AOculusXRSceneActor::RoomLayoutDiscoveryResultsAvailable),
FOculusXRDiscoverAnchorsCompleteDelegate(),
anchorQueryResult);
return anchorQueryResult;
}
void AOculusXRSceneActor::RoomLayoutDiscoveryResultsAvailable(const TArray<FOculusXRAnchorsDiscoverResult>& QueryResults)
{
UE_LOG(LogOculusXRScene, Verbose, TEXT("RoomLayoutDiscoveryResultsAvailable"));
for (auto& QueryElement : QueryResults)
{
ProcessRoomQueryResult(QueryElement.Space, QueryElement.UUID);
}
}
void AOculusXRSceneActor::ProcessRoomQueryResult(FOculusXRUInt64 AnchorHandle, FOculusXRUUID UUID)
{
UE_LOG(LogOculusXRScene, Verbose, TEXT("Process Room Query Result -- Query Element (space = %llu, uuid = %s"), AnchorHandle.Value, *BytesToHex(UUID.UUIDBytes, OCULUSXR_UUID_SIZE));
FOculusXRRoomLayout roomLayout;
const bool bGetRoomLayoutResult = RoomLayoutManagerComponent->GetRoomLayout(AnchorHandle.Value, roomLayout, MaxQueries);
if (!bGetRoomLayoutResult)
{
UE_LOG(LogOculusXRScene, Error, TEXT("Process Room Query Result -- Failed to get room layout for space (space = %llu, uuid = %s"),
AnchorHandle.Value, *BytesToHex(UUID.UUIDBytes, OCULUSXR_UUID_SIZE));
return;
}
roomLayout.RoomAnchorHandle = AnchorHandle;
roomLayout.RoomUuid = UUID;
// If we're only loading the active room we start that floor check query here, otherwise do the room query
if (bActiveRoomOnly)
{
QueryFloorForActiveRoom(AnchorHandle, roomLayout);
}
else
{
StartSingleRoomQuery(AnchorHandle, roomLayout);
}
}
EOculusXRAnchorResult::Type AOculusXRSceneActor::QueryRoomUUIDs(const FOculusXRUInt64 RoomSpaceID, const TArray<FOculusXRUUID>& RoomUUIDs)
{
EOculusXRAnchorResult::Type startAnchorQueryResult;
auto uuidFilter = NewObject<UOculusXRSpaceDiscoveryIdsFilter>(this);
uuidFilter->Uuids = RoomUUIDs;
FOculusXRSpaceDiscoveryInfo discoveryInfo;
discoveryInfo.Filters.Add(uuidFilter);
OculusXRAnchors::FOculusXRAnchors::DiscoverAnchors(discoveryInfo,
FOculusXRDiscoverAnchorsResultsDelegate::CreateUObject(this, &AOculusXRSceneActor::SceneRoomDiscoveryResultsAvailable, RoomSpaceID),
FOculusXRDiscoverAnchorsCompleteDelegate(),
startAnchorQueryResult);
return startAnchorQueryResult;
}
void AOculusXRSceneActor::SceneRoomDiscoveryResultsAvailable(const TArray<FOculusXRAnchorsDiscoverResult>& DiscoveryResults, const FOculusXRUInt64 RoomSpaceID)
{
for (auto& AnchorQueryElement : DiscoveryResults)
{
ProcessRoomElementsResult(AnchorQueryElement.Space, RoomSpaceID);
}
}
void AOculusXRSceneActor::ProcessRoomElementsResult(FOculusXRUInt64 AnchorHandle, const FOculusXRUInt64 RoomSpaceID)
{
bool bOutPending = false;
if (SceneGlobalMeshComponent)
{
TArray<FString> semanticClassifications;
GetSemanticClassifications(AnchorHandle.Value, semanticClassifications);
UE_LOG(LogOculusXRScene, Log, TEXT("SpatialAnchor Scene label is %s"), semanticClassifications.Num() > 0 ? *semanticClassifications[0] : TEXT("unknown"));
if (semanticClassifications.Contains(UOculusXRSceneGlobalMeshComponent::GlobalMeshSemanticLabel))
{
bool bIsTriangleMesh = false;
EOculusXRAnchorResult::Type result = OculusXRAnchors::FOculusXRAnchorManager::GetAnchorComponentStatus(
AnchorHandle.Value, EOculusXRSpaceComponentType::TriangleMesh, bIsTriangleMesh, bOutPending);
if (!UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(result) || !bIsTriangleMesh)
{
UE_LOG(LogOculusXRScene, Error, TEXT("SpatialAnchorQueryResult_Handler Failed to load Triangle Mesh Component for a GLOBAL_MESH"));
return;
}
UClass* sceneAnchorComponentInstanceClass = SceneGlobalMeshComponent->GetAnchorComponentClass();
AActor* globalMeshAnchor = SpawnActorWithSceneComponent(AnchorHandle.Value, RoomSpaceID, semanticClassifications, sceneAnchorComponentInstanceClass);
SceneGlobalMeshComponent->CreateMeshComponent(AnchorHandle, globalMeshAnchor, RoomLayoutManagerComponent);
return;
}
}
bool bIsScenePlane = false;
bool bIsSceneVolume = false;
EOculusXRAnchorResult::Type isPlaneResult = OculusXRAnchors::FOculusXRAnchorManager::GetAnchorComponentStatus(
AnchorHandle.Value, EOculusXRSpaceComponentType::ScenePlane, bIsScenePlane, bOutPending);
EOculusXRAnchorResult::Type isVolumeResult = OculusXRAnchors::FOculusXRAnchorManager::GetAnchorComponentStatus(
AnchorHandle.Value, EOculusXRSpaceComponentType::SceneVolume, bIsSceneVolume, bOutPending);
bool bIsPlaneResultSuccess = UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(isPlaneResult);
bool bIsVolumeResultSuccess = UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(isVolumeResult);
AActor* anchor = nullptr;
if (bIsPlaneResultSuccess && bIsScenePlane)
{
FVector scenePlanePos;
FVector scenePlaneSize;
auto getScenePlaneResult = OculusXRScene::FOculusXRScene::GetScenePlane(AnchorHandle.Value, scenePlanePos, scenePlaneSize);
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(getScenePlaneResult))
{
UE_LOG(LogOculusXRScene, Log, TEXT("SpatialAnchorQueryResult_Handler ScenePlane pos = [%.2f, %.2f, %.2f], size = [%.2f, %.2f, %.2f]."),
scenePlanePos.X, scenePlanePos.Y, scenePlanePos.Z,
scenePlaneSize.X, scenePlaneSize.Y, scenePlaneSize.Z);
TArray<FString> semanticClassifications;
GetSemanticClassifications(AnchorHandle.Value, semanticClassifications);
UE_LOG(LogOculusXRScene, Log, TEXT("SpatialAnchor ScenePlane label is %s"), semanticClassifications.Num() > 0 ? *semanticClassifications[0] : TEXT("unknown"));
anchor = SpawnOrUpdateSceneAnchor(anchor, AnchorHandle, RoomSpaceID, scenePlanePos, scenePlaneSize, semanticClassifications, EOculusXRSpaceComponentType::ScenePlane);
if (!anchor)
{
UE_LOG(LogOculusXRScene, Error, TEXT("SpatialAnchorQueryResult_Handler Failed to spawn scene anchor."));
}
}
else
{
UE_LOG(LogOculusXRScene, Error, TEXT("SpatialAnchorQueryResult_Handler Failed to get bounds for ScenePlane space."));
}
}
if (bIsVolumeResultSuccess && bIsSceneVolume)
{
FVector sceneVolumePos;
FVector sceneVolumeSize;
auto getSceneVolumeResult = OculusXRScene::FOculusXRScene::GetSceneVolume(AnchorHandle.Value, sceneVolumePos, sceneVolumeSize);
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(getSceneVolumeResult))
{
UE_LOG(LogOculusXRScene, Log, TEXT("SpatialAnchorQueryResult_Handler SceneVolume pos = [%.2f, %.2f, %.2f], size = [%.2f, %.2f, %.2f]."),
sceneVolumePos.X, sceneVolumePos.Y, sceneVolumePos.Z,
sceneVolumeSize.X, sceneVolumeSize.Y, sceneVolumeSize.Z);
TArray<FString> semanticClassifications;
GetSemanticClassifications(AnchorHandle.Value, semanticClassifications);
UE_LOG(LogOculusXRScene, Log, TEXT("SpatialAnchor SceneVolume label is %s"), semanticClassifications.Num() > 0 ? *semanticClassifications[0] : TEXT("unknown"));
anchor = SpawnOrUpdateSceneAnchor(anchor, AnchorHandle, RoomSpaceID, sceneVolumePos, sceneVolumeSize, semanticClassifications, EOculusXRSpaceComponentType::SceneVolume);
if (!anchor)
{
UE_LOG(LogOculusXRScene, Error, TEXT("SpatialAnchorQueryResult_Handler Failed to spawn scene anchor."));
}
}
else
{
UE_LOG(LogOculusXRScene, Error, TEXT("SpatialAnchorQueryResult_Handler Failed to get bounds for SceneVolume space."));
}
}
}
void AOculusXRSceneActor::StartSingleRoomQuery(FOculusXRUInt64 RoomSpaceID, FOculusXRRoomLayout RoomLayout)
{
EOculusXRAnchorResult::Type startQueryResult = QueryRoomUUIDs(RoomSpaceID, RoomLayout.RoomObjectUUIDs);
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(startQueryResult))
{
RoomLayouts.Add(RoomSpaceID, std::move(RoomLayout));
}
}
EOculusXRAnchorResult::Type AOculusXRSceneActor::QueryFloorForActiveRoom(FOculusXRUInt64 RoomSpaceID, FOculusXRRoomLayout RoomLayout)
{
EOculusXRAnchorResult::Type anchorQueryResult;
auto uuidFilter = NewObject<UOculusXRSpaceDiscoveryIdsFilter>(this);
uuidFilter->Uuids = { RoomLayout.FloorUuid };
FOculusXRSpaceDiscoveryInfo discoveryInfo;
discoveryInfo.Filters.Add(uuidFilter);
OculusXRAnchors::FOculusXRAnchors::DiscoverAnchors(discoveryInfo,
FOculusXRDiscoverAnchorsResultsDelegate::CreateUObject(this, &AOculusXRSceneActor::ActiveRoomFloorDiscoveryResultsAvailable, RoomSpaceID, RoomLayout),
FOculusXRDiscoverAnchorsCompleteDelegate(),
anchorQueryResult);
return anchorQueryResult;
}
void AOculusXRSceneActor::ActiveRoomFloorDiscoveryResultsAvailable(const TArray<FOculusXRAnchorsDiscoverResult>& DiscoveryResults, FOculusXRUInt64 RoomSpaceID, FOculusXRRoomLayout RoomLayout)
{
if (DiscoveryResults.Num() != 1)
{
UE_LOG(LogOculusXRScene, Error, TEXT("Wrong number of elements returned from discover anchors for floor UUID. Result count (%d), UUID (%s), Room Space ID (%llu)"), DiscoveryResults.Num(), *RoomLayout.FloorUuid.ToString(), RoomSpaceID.Value);
return;
}
const FOculusXRAnchorsDiscoverResult& floorQueryResult = DiscoveryResults[0];
if (!CheckFloorBounds(floorQueryResult.Space, floorQueryResult.UUID, RoomLayout.RoomAnchorHandle))
{
UE_LOG(LogOculusXRScene, Verbose, TEXT("User is not within bounds of queried floor in room. UUID (%s), Room Space ID (%llu)"), *floorQueryResult.UUID.ToString(), RoomLayout.RoomAnchorHandle.GetValue());
return;
}
StartSingleRoomQuery(RoomSpaceID, RoomLayout);
}
bool AOculusXRSceneActor::CheckFloorBounds(FOculusXRUInt64 AnchorHandle, FOculusXRUUID UUID, FOculusXRUInt64 RoomAnchorHandle)
{
TArray<FString> semanticClassifications;
GetSemanticClassifications(AnchorHandle, semanticClassifications);
if (!semanticClassifications.Contains("FLOOR"))
{
UE_LOG(LogOculusXRScene, Error, TEXT("Queried floor in room doesn't contain a floor semantic label. UUID (%s), Room Space ID (%llu)"), *UUID.ToString(), RoomAnchorHandle.GetValue());
return false;
}
TArray<FVector2f> boundaryVertices;
EOculusXRAnchorResult::Type getBoundaryResult = OculusXRScene::FOculusXRScene::GetBoundary2D(AnchorHandle, boundaryVertices);
if (!UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(getBoundaryResult))
{
UE_LOG(LogOculusXRScene, Error, TEXT("Failed to get space boundary vertices for floor. UUID (%s), Room Space ID (%llu)"), *UUID.ToString(), RoomAnchorHandle.GetValue());
return false;
}
FQuat HMDOrientation;
FVector HMDPosition;
GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, HMDOrientation, HMDPosition);
float scale = GEngine->XRSystem->GetWorldToMetersScale();
TArray<FVector> convertedBoundaryPoints;
FTransform floorTransform;
if (!UOculusXRAnchorBPFunctionLibrary::GetAnchorTransformByHandle(AnchorHandle, floorTransform))
{
UE_LOG(LogOculusXRScene, Error, TEXT("Failed to get the floor anchor transform. Floor Space ID (%llu)"), AnchorHandle.GetValue());
return false;
}
// Convert the boundary vertices to game engine world space
for (auto& it : boundaryVertices)
{
FVector pos = floorTransform.TransformPosition(FVector(0, it.X * scale, it.Y * scale));
convertedBoundaryPoints.Add(pos);
}
// Create the new 2D boundary
TArray<FVector2f> new2DBoundary;
for (auto& it : convertedBoundaryPoints)
{
new2DBoundary.Add(FVector2f(it.X, it.Y));
}
// Check if inside poly
if (!PointInPolygon2D(FVector2f(HMDPosition.X, HMDPosition.Y), new2DBoundary))
{
UE_LOG(LogOculusXRScene, Verbose, TEXT("Floor failed active room check. UUID (%s), Room Space ID (%llu)"), *UUID.ToString(), RoomAnchorHandle.GetValue());
return false;
}
return true;
}
bool AOculusXRSceneActor::PointInPolygon2D(FVector2f PointToTest, const TArray<FVector2f>& PolyVerts) const
{
if (PolyVerts.Num() < 3)
{
return false;
}
int collision = 0;
float x = PointToTest.X;
float y = PointToTest.Y;
int vertCount = PolyVerts.Num();
for (int i = 0; i < vertCount; i++)
{
float x1 = PolyVerts[i].X;
float y1 = PolyVerts[i].Y;
float x2 = PolyVerts[(i + 1) % vertCount].X;
float y2 = PolyVerts[(i + 1) % vertCount].Y;
if (y < y1 != y < y2 && x < x1 + ((y - y1) / (y2 - y1)) * (x2 - x1))
{
collision += (y1 < y2) ? 1 : -1;
}
}
return collision != 0;
}
void AOculusXRSceneActor::GetSemanticClassifications(uint64 Space, TArray<FString>& OutSemanticLabels) const
{
EOculusXRAnchorResult::Type semanticLabelAnchorResult = OculusXRScene::FOculusXRScene::GetSemanticClassification(Space, OutSemanticLabels);
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(semanticLabelAnchorResult))
{
UE_LOG(LogOculusXRScene, Verbose, TEXT("GetSemanticClassifications -- Space (%llu) Classifications:"), Space);
for (FString& label : OutSemanticLabels)
{
UE_LOG(LogOculusXRScene, Verbose, TEXT("%s"), *label);
}
}
else
{
UE_LOG(LogOculusXRScene, Error, TEXT("SpatialAnchorQueryResult_Handler Failed to get semantic classification space."));
}
}
// DELEGATE HANDLERS
void AOculusXRSceneActor::SceneCaptureComplete_Handler(FOculusXRUInt64 RequestId, bool bResult)
{
if (!bResult)
{
UE_LOG(LogOculusXRScene, Error, TEXT("Scene Capture Complete failed!"));
return;
}
// Mark that we already launched Capture Flow and try to query spatial anchors again
bCaptureFlowWasLaunched = true;
ClearScene();
PopulateScene();
}
void AOculusXRSceneActor::PostLoad()
{
Super::PostLoad();
FOculusXRSpawnedSceneAnchorProperties desk;
if (ScenePlaneSpawnedSceneAnchorProperties.RemoveAndCopyValue(TEXT("DESK"), desk))
{
UE_LOG(LogOculusXRScene, Log, TEXT("Running XR Scene Actor plane semantic lable migration: 'DESK' to 'TABLE'"));
ScenePlaneSpawnedSceneAnchorProperties[TEXT("TABLE")] = desk;
}
if (SceneVolumeSpawnedSceneAnchorProperties.RemoveAndCopyValue(TEXT("DESK"), desk))
{
UE_LOG(LogOculusXRScene, Log, TEXT("Running XR Scene Actor volume semantic lable migration: 'DESK' to 'TABLE'"));
SceneVolumeSpawnedSceneAnchorProperties[TEXT("TABLE")] = desk;
}
}
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,21 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRSceneAnchorComponent.h"
#include "Engine/StaticMeshActor.h"
UOculusXRSceneAnchorComponent::UOculusXRSceneAnchorComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
bUpdateHeadSpaceTransform = false;
}
void UOculusXRSceneAnchorComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (GetHandle().Value == 0)
{
return;
}
}

View File

@@ -0,0 +1,59 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRSceneComponents.h"
#include "OculusXRScene.h"
#include "OculusXRSceneModule.h"
#include "OculusXRAnchorBPFunctionLibrary.h"
bool UOculusXRPlaneAnchorComponent::GetPositionAndSize(FVector& outPosition, FVector& outSize) const
{
ensure(IsComponentEnabled());
if (!UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OculusXRScene::FOculusXRScene::GetScenePlane(Space, outPosition, outSize)))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("Fetching scene plane failed."));
return false;
}
return true;
}
bool UOculusXRVolumeAnchorComponent::GetPositionAndSize(FVector& outPosition, FVector& outSize) const
{
ensure(IsComponentEnabled());
if (!UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OculusXRScene::FOculusXRScene::GetSceneVolume(Space, outPosition, outSize)))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("Fetching scene plane failed."));
return false;
}
return true;
}
bool UOculusXRSemanticClassificationAnchorComponent::GetSemanticClassifications(TArray<FString>& outClassifications) const
{
ensure(IsComponentEnabled());
if (!UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OculusXRScene::FOculusXRScene::GetSemanticClassification(Space, outClassifications)))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("Fetching scene volume failed."));
return false;
}
return true;
}
bool UOculusXRRoomLayoutAnchorComponent::GetRoomLayout(FOculusXRUUID& outFloorUUID, FOculusXRUUID& outCeilingUUID, TArray<FOculusXRUUID>& outWallsUUIDs) const
{
ensure(IsComponentEnabled());
auto result = OculusXRScene::FOculusXRScene::GetRoomLayout(Space, 64, outCeilingUUID, outFloorUUID, outWallsUUIDs);
if (!UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(result))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("Fetching room layout failed."));
return false;
}
return true;
}

View File

@@ -0,0 +1,7 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRSceneDelegates.h"
FOculusXRSceneEventDelegates::FOculusXRBoundaryVisibilityChanged FOculusXRSceneEventDelegates::OculusBoundaryVisibilityChanged;
FOculusXRSceneEventDelegates::FOculusXRSceneCaptureCompleteDelegate FOculusXRSceneEventDelegates::OculusSceneCaptureComplete;

View File

@@ -0,0 +1,83 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRSceneEventHandling.h"
#include "OculusXRHMD.h"
#include "IOculusXRSceneModule.h"
#include "OculusXRAnchorBPFunctionLibrary.h"
#include "OculusXRSceneDelegates.h"
#include "OculusXRSceneEventDelegates.h"
#include "OculusXRSceneModule.h"
namespace OculusXRScene
{
template <typename T>
void GetEventData(ovrpEventDataBuffer& Buffer, T& OutEventData)
{
unsigned char* BufData = Buffer.EventData;
BufData -= sizeof(Buffer.EventType); // Offset buffer data to get to the actual event payload
memcpy(&OutEventData, BufData, sizeof(T));
}
void FOculusXRSceneEventHandling::OnPollEvent(ovrpEventDataBuffer* EventDataBuffer, bool& EventPollResult)
{
ovrpEventDataBuffer& buf = *EventDataBuffer;
EventPollResult = true;
switch (buf.EventType)
{
case ovrpEventType_SceneCaptureComplete:
{
ovrpEventSceneCaptureComplete sceneCaptureComplete;
unsigned char* bufData = buf.EventData;
memcpy(&sceneCaptureComplete.requestId, bufData, sizeof(sceneCaptureComplete.requestId));
bufData += sizeof(ovrpUInt64); // move forward
memcpy(&sceneCaptureComplete.result, bufData, sizeof(sceneCaptureComplete.result));
FOculusXRSceneEventDelegates::OculusSceneCaptureComplete.Broadcast(FOculusXRUInt64(sceneCaptureComplete.requestId), sceneCaptureComplete.result >= 0);
break;
}
case ovrpEventType_BoundaryVisibilityChanged:
{
ovrpEventDataBoundaryVisibilityChanged visibilityChangedEvent;
GetEventData(buf, visibilityChangedEvent);
ovrpBoundaryVisibility newVisibility = visibilityChangedEvent.BoundaryVisibility;
EOculusXRBoundaryVisibility ueVisibility = EOculusXRBoundaryVisibility::Invalid;
switch (newVisibility)
{
case ovrpBoundaryVisibility_Suppressed:
ueVisibility = EOculusXRBoundaryVisibility::Suppressed;
break;
case ovrpBoundaryVisibility_NotSuppressed:
ueVisibility = EOculusXRBoundaryVisibility::NotSuppressed;
break;
default:
UE_LOG(LogOculusXRScene, Error, TEXT("Unknown ovrp boundary type in BoundaryVisibilityChanged event! Enum value(%d)"), newVisibility);
}
UE_LOG(LogOculusXRScene, Log, TEXT("FOculusXRSceneEventHandling - Boundary visibility changed. Visibility(%s)"), *UEnum::GetValueAsString(ueVisibility));
FOculusXRSceneEventDelegates::OculusBoundaryVisibilityChanged.Broadcast(ueVisibility);
UOculusXRSceneEventDelegates* eventDelegates = GEngine->GetEngineSubsystem<UOculusXRSceneEventDelegates>();
if (eventDelegates != nullptr)
{
eventDelegates->OnBoundaryVisibilityChanged.Broadcast(ueVisibility);
}
break;
}
case ovrpEventType_None:
default:
{
EventPollResult = false;
break;
}
}
}
} // namespace OculusXRScene

View File

@@ -0,0 +1,19 @@
/*
Copyright (c) Meta Platforms, Inc. and affiliates.
All rights reserved.
This source code is licensed under the license found in the
LICENSE file in the root directory of this source tree.
*/
#pragma once
#include "CoreMinimal.h"
#include "OculusXRHMDPrivate.h"
namespace OculusXRScene
{
struct OCULUSXRSCENE_API FOculusXRSceneEventHandling
{
static void OnPollEvent(ovrpEventDataBuffer* EventDataBuffer, bool& EventPollResult);
};
} // namespace OculusXRScene

View File

@@ -0,0 +1,101 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRSceneFunctionLibrary.h"
#include "Engine/GameInstance.h"
#include "OculusXRAnchorBPFunctionLibrary.h"
#include "OculusXRScene.h"
#include "OculusXRSceneModule.h"
#include "OculusXRSceneSubsystem.h"
#include "OculusXRAnchors.h"
#include "OculusXRHMDPrivate.h"
#include "OculusXRHMD.h"
bool UOculusXRSceneFunctionLibrary::GetBoundaryVisibility(const UObject* WorldContext, EOculusXRBoundaryVisibility& OutVisibility)
{
OutVisibility = EOculusXRBoundaryVisibility::NotSuppressed;
check(WorldContext);
check(WorldContext->GetWorld());
UOculusXRSceneSubsystem* subsystem = WorldContext->GetWorld()->GetGameInstance()->GetSubsystem<UOculusXRSceneSubsystem>();
if (subsystem == nullptr)
{
return false;
}
OutVisibility = subsystem->GetBoundaryVisibility();
return true;
}
bool UOculusXRSceneFunctionLibrary::GetRequestedBoundaryVisibility(const UObject* WorldContext, EOculusXRBoundaryVisibility& OutVisibility)
{
OutVisibility = EOculusXRBoundaryVisibility::NotSuppressed;
check(WorldContext);
check(WorldContext->GetWorld());
UOculusXRSceneSubsystem* subsystem = WorldContext->GetWorld()->GetGameInstance()->GetSubsystem<UOculusXRSceneSubsystem>();
if (subsystem == nullptr)
{
return false;
}
OutVisibility = subsystem->GetRequestedBoundaryVisibility();
return true;
}
bool UOculusXRSceneFunctionLibrary::RequestBoundaryVisibility(const UObject* WorldContext, EOculusXRBoundaryVisibility Visibility)
{
check(WorldContext);
check(WorldContext->GetWorld());
UOculusXRSceneSubsystem* subsystem = WorldContext->GetWorld()->GetGameInstance()->GetSubsystem<UOculusXRSceneSubsystem>();
if (subsystem == nullptr)
{
return false;
}
subsystem->SetRequestedBoundaryVisibility(Visibility);
return true;
}
bool UOculusXRSceneFunctionLibrary::GetRoomLayout(FOculusXRUInt64 Space, FOculusXRRoomLayout& RoomLayoutOut, int32 MaxWallsCapacity)
{
if (MaxWallsCapacity <= 0)
{
return false;
}
FOculusXRUUID OutCeilingUuid;
FOculusXRUUID OutFloorUuid;
TArray<FOculusXRUUID> OutWallsUuid;
auto result = OculusXRScene::FOculusXRScene::GetRoomLayout(Space.Value, static_cast<uint32>(MaxWallsCapacity), OutCeilingUuid, OutFloorUuid, OutWallsUuid);
auto bSuccess = UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(result);
if (bSuccess)
{
RoomLayoutOut.CeilingUuid = OutCeilingUuid;
RoomLayoutOut.FloorUuid = OutFloorUuid;
RoomLayoutOut.WallsUuid.InsertZeroed(0, OutWallsUuid.Num());
for (int32 i = 0; i < OutWallsUuid.Num(); ++i)
{
RoomLayoutOut.WallsUuid[i] = OutWallsUuid[i];
}
TArray<FOculusXRUUID> spaceUUIDs;
EOculusXRAnchorResult::Type getContainerUUIDsResult;
OculusXRAnchors::FOculusXRAnchors::GetSpaceContainerUUIDs(Space, spaceUUIDs, getContainerUUIDsResult);
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(getContainerUUIDsResult))
{
RoomLayoutOut.RoomObjectUUIDs = spaceUUIDs;
}
}
return bSuccess;
}

View File

@@ -0,0 +1,282 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRSceneFunctionsOVR.h"
#include "OculusXRSceneModule.h"
#include "OculusXRHMDPrivate.h"
#include "OculusXRHMD.h"
#include "OculusXRPluginWrapper.h"
#define LOCTEXT_NAMESPACE "OculusXRScene"
namespace OculusXRScene
{
EOculusXRAnchorResult::Type FOculusXRSceneFunctionsOVR::GetScenePlane(uint64 AnchorHandle, FVector& OutPos, FVector& OutSize)
{
OutPos.X = OutPos.Y = OutPos.Z = 0.f;
OutSize.X = OutSize.Y = OutSize.Z = 0.f;
ovrpRectf rect;
const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceBoundingBox2D(&AnchorHandle, &rect);
if (OVRP_SUCCESS(Result))
{
// Convert to UE's coordinates system
OutPos.Y = rect.Pos.x;
OutPos.Z = rect.Pos.y;
OutSize.Y = rect.Size.w;
OutSize.Z = rect.Size.h;
}
return static_cast<EOculusXRAnchorResult::Type>(Result);
}
EOculusXRAnchorResult::Type FOculusXRSceneFunctionsOVR::GetSceneVolume(uint64 AnchorHandle, FVector& OutPos, FVector& OutSize)
{
OutPos.X = OutPos.Y = OutPos.Z = 0.f;
OutSize.X = OutSize.Y = OutSize.Z = 0.f;
ovrpBoundsf bounds;
const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceBoundingBox3D(&AnchorHandle, &bounds);
if (OVRP_SUCCESS(Result))
{
// Convert from OpenXR's right-handed to Unreal's left-handed coordinate system.
// OpenXR Unreal
// | y | z
// | |
// z <----+ +----> x
// / /
// x/ y/
//
OutPos.X = -bounds.Pos.z;
OutPos.Y = bounds.Pos.x;
OutPos.Z = bounds.Pos.y;
// The position represents the corner of the volume which has the lowest value
// of each axis. Since we flipped the sign of one of the axes we need to adjust
// the position to the other side of the volume
OutPos.X -= bounds.Size.d;
// We keep the size positive for all dimensions
OutSize.X = bounds.Size.d;
OutSize.Y = bounds.Size.w;
OutSize.Z = bounds.Size.h;
}
return static_cast<EOculusXRAnchorResult::Type>(Result);
}
EOculusXRAnchorResult::Type FOculusXRSceneFunctionsOVR::GetSemanticClassification(uint64 AnchorHandle, TArray<FString>& OutSemanticClassifications)
{
OutSemanticClassifications.Empty();
const int32 maxByteSize = 1024;
char labelsChars[maxByteSize];
ovrpSemanticLabels labels;
labels.byteCapacityInput = maxByteSize;
labels.labels = labelsChars;
const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceSemanticLabels(&AnchorHandle, &labels);
if (OVRP_SUCCESS(Result))
{
FString labelsStr(labels.byteCountOutput, labels.labels);
labelsStr.ParseIntoArray(OutSemanticClassifications, TEXT(","));
}
return static_cast<EOculusXRAnchorResult::Type>(Result);
}
EOculusXRAnchorResult::Type FOculusXRSceneFunctionsOVR::GetBoundary2D(uint64 AnchorHandle, TArray<FVector2f>& OutVertices)
{
TArray<ovrpVector2f> vertices;
// Get the number of elements in the container
ovrpBoundary2D boundary;
boundary.vertexCapacityInput = 0;
boundary.vertexCountOutput = 0;
boundary.vertices = nullptr;
ovrpResult result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceBoundary2D(&AnchorHandle, &boundary);
if (OVRP_FAILURE(result))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("Failed to get space boundary 2d %d"), result);
return static_cast<EOculusXRAnchorResult::Type>(result);
}
// Retrieve the actual array of vertices
vertices.SetNum(boundary.vertexCountOutput);
boundary.vertexCapacityInput = boundary.vertexCountOutput;
boundary.vertices = vertices.GetData();
result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceBoundary2D(&AnchorHandle, &boundary);
if (OVRP_FAILURE(result))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("Failed to get space boundary 2d %d"), result);
return static_cast<EOculusXRAnchorResult::Type>(result);
}
// Write out the vertices
OutVertices.Reserve(vertices.Num());
for (const auto& it : vertices)
{
OutVertices.Add(FVector2f(it.x, it.y));
}
return EOculusXRAnchorResult::Success;
}
EOculusXRAnchorResult::Type FOculusXRSceneFunctionsOVR::RequestSceneCapture(uint64& OutRequestID)
{
OutRequestID = 0;
ovrpSceneCaptureRequest sceneCaptureRequest;
sceneCaptureRequest.request = nullptr;
sceneCaptureRequest.requestByteCount = 0;
ovrpResult result = FOculusXRHMDModule::GetPluginWrapper().RequestSceneCapture(&sceneCaptureRequest, &OutRequestID);
return static_cast<EOculusXRAnchorResult::Type>(result);
}
EOculusXRAnchorResult::Type FOculusXRSceneFunctionsOVR::GetRoomLayout(uint64 AnchorHandle, const uint32 MaxWallsCapacity, FOculusXRUUID& OutCeilingUuid, FOculusXRUUID& OutFloorUuid, TArray<FOculusXRUUID>& OutWallsUuid)
{
ovrpRoomLayout roomLayout;
roomLayout.wallUuidCapacityInput = 0;
roomLayout.wallUuidCountOutput = 0;
// First call to get output size
ovrpResult firstCallResult = FOculusXRHMDModule::GetPluginWrapper().GetSpaceRoomLayout(&AnchorHandle, &roomLayout);
if (OVRP_FAILURE(firstCallResult))
{
return static_cast<EOculusXRAnchorResult::Type>(firstCallResult);
}
// Set the input size and pointer to the uuid array
TArray<ovrpUuid> uuids;
uuids.InsertZeroed(0, roomLayout.wallUuidCountOutput);
roomLayout.wallUuidCapacityInput = roomLayout.wallUuidCountOutput;
roomLayout.wallUuids = uuids.GetData();
ovrpResult secondCallResult = FOculusXRHMDModule::GetPluginWrapper().GetSpaceRoomLayout(&AnchorHandle, &roomLayout);
if (OVRP_FAILURE(secondCallResult))
{
return static_cast<EOculusXRAnchorResult::Type>(secondCallResult);
}
OutCeilingUuid = FOculusXRUUID(roomLayout.ceilingUuid.data);
OutFloorUuid = FOculusXRUUID(roomLayout.floorUuid.data);
OutWallsUuid.Empty();
OutWallsUuid.InsertZeroed(0, uuids.Num());
for (int32 i = 0; i < uuids.Num(); ++i)
{
OutWallsUuid[i] = FOculusXRUUID(roomLayout.wallUuids[i].data);
}
return static_cast<EOculusXRAnchorResult::Type>(secondCallResult);
}
EOculusXRAnchorResult::Type FOculusXRSceneFunctionsOVR::GetTriangleMesh(uint64 AnchorHandle, TArray<FVector>& Vertices, TArray<int32>& Triangles)
{
ovrpTriangleMesh OVRPMesh = { 0, 0, nullptr, 0, 0, nullptr };
ovrpResult countResult = FOculusXRHMDModule::GetPluginWrapper().GetSpaceTriangleMesh(&AnchorHandle, &OVRPMesh);
if (OVRP_FAILURE(countResult))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("Failed to load TriangleMesh info - Space: %llu - Result: %d"), AnchorHandle, countResult);
return static_cast<EOculusXRAnchorResult::Type>(countResult);
}
OVRPMesh.indexCapacityInput = OVRPMesh.indexCountOutput;
OVRPMesh.vertexCapacityInput = OVRPMesh.vertexCountOutput;
TArray<ovrpVector3f> OVRPVertices;
OVRPVertices.SetNum(OVRPMesh.vertexCapacityInput);
OVRPMesh.vertices = OVRPVertices.GetData();
Triangles.SetNum(OVRPMesh.indexCapacityInput);
check(sizeof(TRemoveReference<decltype(Triangles)>::Type::ElementType) == sizeof(TRemovePointer<decltype(OVRPMesh.indices)>::Type));
OVRPMesh.indices = Triangles.GetData();
const ovrpResult meshResult = FOculusXRHMDModule::GetPluginWrapper().GetSpaceTriangleMesh(&AnchorHandle, &OVRPMesh);
if (OVRP_FAILURE(meshResult))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("Failed to load TriangleMesh data - AnchorHandle: %llu - Result: %d"), AnchorHandle, meshResult);
return static_cast<EOculusXRAnchorResult::Type>(meshResult);
}
UE_LOG(LogOculusXRScene, Verbose, TEXT("Loaded TriangleMesh data - AnchorHandle: %llu - Vertices: %d - Faces: %d"),
AnchorHandle, OVRPMesh.vertexCapacityInput, OVRPMesh.indexCapacityInput);
Vertices.Empty(OVRPVertices.Num());
Algo::Transform(OVRPVertices, Vertices, [](const auto& Vertex) { return OculusXRHMD::ToFVector(Vertex); });
return static_cast<EOculusXRAnchorResult::Type>(meshResult);
}
// Requests to change the current boundary visibility
EOculusXRAnchorResult::Type FOculusXRSceneFunctionsOVR::RequestBoundaryVisibility(EOculusXRBoundaryVisibility NewVisibilityRequest)
{
ovrpBoundaryVisibility visibility = ovrpBoundaryVisibility_NotSuppressed;
switch (NewVisibilityRequest)
{
case EOculusXRBoundaryVisibility::Suppressed:
visibility = ovrpBoundaryVisibility_Suppressed;
break;
case EOculusXRBoundaryVisibility::NotSuppressed:
visibility = ovrpBoundaryVisibility_NotSuppressed;
break;
default:
UE_LOG(LogOculusXRScene, Error, TEXT("RequestBoundaryVisibility -- Unknown boundary visibility value! (%d)"), static_cast<int32>(NewVisibilityRequest));
return EOculusXRAnchorResult::Failure_InvalidParameter;
}
UE_LOG(LogOculusXRScene, Log, TEXT("RequestBoundaryVisibility -- New Visibility Requested (%s)"), *UEnum::GetValueAsString(NewVisibilityRequest));
auto result = FOculusXRHMDModule::GetPluginWrapper().RequestBoundaryVisibility(visibility);
auto castedResult = static_cast<EOculusXRAnchorResult::Type>(result);
if (!OVRP_SUCCESS(result))
{
UE_LOG(LogOculusXRScene, Error, TEXT("RequestBoundaryVisibility failed -- Result(%s)"), *UEnum::GetValueAsString(castedResult));
}
return castedResult;
}
EOculusXRAnchorResult::Type FOculusXRSceneFunctionsOVR::GetBoundaryVisibility(EOculusXRBoundaryVisibility& OutVisibility)
{
ovrpBoundaryVisibility visibility = {};
auto result = FOculusXRHMDModule::GetPluginWrapper().GetBoundaryVisibility(&visibility);
auto castedResult = static_cast<EOculusXRAnchorResult::Type>(result);
if (OVRP_SUCCESS(result))
{
switch (visibility)
{
case ovrpBoundaryVisibility_Suppressed:
OutVisibility = EOculusXRBoundaryVisibility::Suppressed;
break;
case ovrpBoundaryVisibility_NotSuppressed:
OutVisibility = EOculusXRBoundaryVisibility::NotSuppressed;
break;
default:
OutVisibility = EOculusXRBoundaryVisibility::Invalid;
UE_LOG(LogOculusXRScene, Error, TEXT("GetBoundaryVisibility -- Unknown boundary visibility value! Value(%d)"), visibility);
break;
}
}
else
{
UE_LOG(LogOculusXRScene, Warning, TEXT("GetBoundaryVisibility -- Failed to get boundary visibility. Result(%s)"), *UEnum::GetValueAsString(castedResult));
}
return castedResult;
}
} // namespace OculusXRScene
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,27 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "OculusXRSceneFunctions.h"
namespace OculusXRScene
{
struct OCULUSXRSCENE_API FOculusXRSceneFunctionsOVR : public IOculusXRSceneFunctions
{
virtual EOculusXRAnchorResult::Type GetScenePlane(uint64 AnchorHandle, FVector& OutPos, FVector& OutSize) override;
virtual EOculusXRAnchorResult::Type GetSceneVolume(uint64 AnchorHandle, FVector& OutPos, FVector& OutSize) override;
virtual EOculusXRAnchorResult::Type GetSemanticClassification(uint64 AnchorHandle, TArray<FString>& OutSemanticClassifications) override;
virtual EOculusXRAnchorResult::Type GetBoundary2D(uint64 AnchorHandle, TArray<FVector2f>& OutVertices) override;
virtual EOculusXRAnchorResult::Type RequestSceneCapture(uint64& OutRequestID) override;
virtual EOculusXRAnchorResult::Type GetRoomLayout(uint64 AnchorHandle, const uint32 MaxWallsCapacity, FOculusXRUUID& OutCeilingUuid, FOculusXRUUID& OutFloorUuid, TArray<FOculusXRUUID>& OutWallsUuid) override;
virtual EOculusXRAnchorResult::Type GetTriangleMesh(uint64 AnchorHandle, TArray<FVector>& Vertices, TArray<int32>& Triangles) override;
// Requests to change the current boundary visibility
virtual EOculusXRAnchorResult::Type RequestBoundaryVisibility(EOculusXRBoundaryVisibility NewVisibilityRequest) override;
// Gets the current boundary visibility
virtual EOculusXRAnchorResult::Type GetBoundaryVisibility(EOculusXRBoundaryVisibility& OutVisibility) override;
};
} // namespace OculusXRScene

View File

@@ -0,0 +1,71 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRSceneFunctionsOpenXR.h"
#include "OculusXRSceneModule.h"
#include "OculusXRHMDPrivate.h"
#include "OculusXRHMD.h"
#include "OculusXRPluginWrapper.h"
#include "OculusXRAnchorsUtil.h"
#define LOCTEXT_NAMESPACE "OculusXRScene"
namespace OculusXRScene
{
EOculusXRAnchorResult::Type FOculusXRSceneFunctionsOpenXR::GetScenePlane(uint64 AnchorHandle, FVector& OutPos, FVector& OutSize)
{
auto result = FOculusXRSceneModule::Get().GetXrScene()->GetScenePlane(AnchorHandle, OutPos, OutSize);
return OculusXRAnchors::GetResultFromXrResult(result);
}
EOculusXRAnchorResult::Type FOculusXRSceneFunctionsOpenXR::GetSceneVolume(uint64 AnchorHandle, FVector& OutPos, FVector& OutSize)
{
auto result = FOculusXRSceneModule::Get().GetXrScene()->GetSceneVolume(AnchorHandle, OutPos, OutSize);
return OculusXRAnchors::GetResultFromXrResult(result);
}
EOculusXRAnchorResult::Type FOculusXRSceneFunctionsOpenXR::GetSemanticClassification(uint64 AnchorHandle, TArray<FString>& OutSemanticClassifications)
{
auto result = FOculusXRSceneModule::Get().GetXrScene()->GetSemanticClassification(AnchorHandle, OutSemanticClassifications);
return OculusXRAnchors::GetResultFromXrResult(result);
}
EOculusXRAnchorResult::Type FOculusXRSceneFunctionsOpenXR::GetBoundary2D(uint64 AnchorHandle, TArray<FVector2f>& OutVertices)
{
auto result = FOculusXRSceneModule::Get().GetXrScene()->GetBoundary2D(AnchorHandle, OutVertices);
return OculusXRAnchors::GetResultFromXrResult(result);
}
EOculusXRAnchorResult::Type FOculusXRSceneFunctionsOpenXR::RequestSceneCapture(uint64& OutRequestID)
{
auto result = FOculusXRSceneModule::Get().GetXrScene()->RequestSceneCapture(OutRequestID);
return OculusXRAnchors::GetResultFromXrResult(result);
}
EOculusXRAnchorResult::Type FOculusXRSceneFunctionsOpenXR::GetRoomLayout(uint64 AnchorHandle, const uint32 MaxWallsCapacity, FOculusXRUUID& OutCeilingUuid, FOculusXRUUID& OutFloorUuid, TArray<FOculusXRUUID>& OutWallsUuid)
{
auto result = FOculusXRSceneModule::Get().GetXrScene()->GetRoomLayout(AnchorHandle, MaxWallsCapacity, OutCeilingUuid, OutFloorUuid, OutWallsUuid);
return OculusXRAnchors::GetResultFromXrResult(result);
}
EOculusXRAnchorResult::Type FOculusXRSceneFunctionsOpenXR::GetTriangleMesh(uint64 AnchorHandle, TArray<FVector>& Vertices, TArray<int32>& Triangles)
{
auto result = FOculusXRSceneModule::Get().GetXrScene()->GetTriangleMesh(AnchorHandle, Vertices, Triangles);
return OculusXRAnchors::GetResultFromXrResult(result);
}
// Requests to change the current boundary visibility
EOculusXRAnchorResult::Type FOculusXRSceneFunctionsOpenXR::RequestBoundaryVisibility(EOculusXRBoundaryVisibility NewVisibilityRequest)
{
auto result = FOculusXRSceneModule::Get().GetXrScene()->RequestBoundaryVisibility(NewVisibilityRequest);
return OculusXRAnchors::GetResultFromXrResult(result);
}
EOculusXRAnchorResult::Type FOculusXRSceneFunctionsOpenXR::GetBoundaryVisibility(EOculusXRBoundaryVisibility& OutVisibility)
{
auto result = FOculusXRSceneModule::Get().GetXrScene()->GetBoundaryVisibility(OutVisibility);
return OculusXRAnchors::GetResultFromXrResult(result);
}
} // namespace OculusXRScene
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,27 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "OculusXRSceneFunctions.h"
namespace OculusXRScene
{
struct OCULUSXRSCENE_API FOculusXRSceneFunctionsOpenXR : public IOculusXRSceneFunctions
{
virtual EOculusXRAnchorResult::Type GetScenePlane(uint64 AnchorHandle, FVector& OutPos, FVector& OutSize) override;
virtual EOculusXRAnchorResult::Type GetSceneVolume(uint64 AnchorHandle, FVector& OutPos, FVector& OutSize) override;
virtual EOculusXRAnchorResult::Type GetSemanticClassification(uint64 AnchorHandle, TArray<FString>& OutSemanticClassifications) override;
virtual EOculusXRAnchorResult::Type GetBoundary2D(uint64 AnchorHandle, TArray<FVector2f>& OutVertices) override;
virtual EOculusXRAnchorResult::Type RequestSceneCapture(uint64& OutRequestID) override;
virtual EOculusXRAnchorResult::Type GetRoomLayout(uint64 AnchorHandle, const uint32 MaxWallsCapacity, FOculusXRUUID& OutCeilingUuid, FOculusXRUUID& OutFloorUuid, TArray<FOculusXRUUID>& OutWallsUuid) override;
virtual EOculusXRAnchorResult::Type GetTriangleMesh(uint64 AnchorHandle, TArray<FVector>& Vertices, TArray<int32>& Triangles) override;
// Requests to change the current boundary visibility
virtual EOculusXRAnchorResult::Type RequestBoundaryVisibility(EOculusXRBoundaryVisibility NewVisibilityRequest) override;
// Gets the current boundary visibility
virtual EOculusXRAnchorResult::Type GetBoundaryVisibility(EOculusXRBoundaryVisibility& OutVisibility) override;
};
} // namespace OculusXRScene

View File

@@ -0,0 +1,67 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRSceneGlobalMeshComponent.h"
#include "OculusXRSceneModule.h"
#include "OculusXRRoomLayoutManagerComponent.h"
#include "ProceduralMeshComponent.h"
#include "Engine/World.h"
#include "GameFramework/WorldSettings.h"
#include "Materials/MaterialInterface.h"
const FString UOculusXRSceneGlobalMeshComponent::GlobalMeshSemanticLabel = TEXT("GLOBAL_MESH");
UOculusXRSceneGlobalMeshComponent::UOculusXRSceneGlobalMeshComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void UOculusXRSceneGlobalMeshComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}
bool UOculusXRSceneGlobalMeshComponent::HasCollision() const
{
return Collision;
}
bool UOculusXRSceneGlobalMeshComponent::IsVisible() const
{
return Visible;
}
UClass* UOculusXRSceneGlobalMeshComponent::GetAnchorComponentClass() const
{
UClass* sceneAnchorComponentInstanceClass = SceneAnchorComponent ? SceneAnchorComponent.LoadSynchronous() : nullptr;
return sceneAnchorComponentInstanceClass;
}
void UOculusXRSceneGlobalMeshComponent::CreateMeshComponent(const FOculusXRUInt64& Space, AActor* GlobalMeshAnchor, const UOculusXRRoomLayoutManagerComponent* RoomLayoutManagerComponent) const
{
bool hasCollision = HasCollision();
UProceduralMeshComponent* proceduralMeshComponent = NewObject<UProceduralMeshComponent>(GlobalMeshAnchor);
proceduralMeshComponent->RegisterComponent();
bool bLoaded = RoomLayoutManagerComponent->LoadTriangleMesh(Space.Value, proceduralMeshComponent, hasCollision);
ensure(bLoaded);
UMaterialInterface* refMaterial = Material;
if (refMaterial != nullptr)
{
UE_LOG(LogOculusXRScene, Verbose, TEXT("GLOBAL MESH Set Material %s"), *refMaterial->GetName());
proceduralMeshComponent->SetMaterial(0, refMaterial);
}
if (hasCollision)
{
FName refCollisionProfile = CollisionProfileName.Name;
proceduralMeshComponent->SetCollisionProfileName(refCollisionProfile);
UE_LOG(LogOculusXRScene, Verbose, TEXT("GLOBAL MESH Set Collision Profile %s"), *refCollisionProfile.ToString());
}
GlobalMeshAnchor->AddOwnedComponent(proceduralMeshComponent);
proceduralMeshComponent->AttachToComponent(GlobalMeshAnchor->GetRootComponent(), FAttachmentTransformRules::KeepWorldTransform);
proceduralMeshComponent->SetRelativeLocation(FVector::ZeroVector, false, nullptr, ETeleportType::ResetPhysics);
proceduralMeshComponent->SetVisibility(IsVisible());
const float worldToMeters = GetWorld()->GetWorldSettings()->WorldToMeters;
proceduralMeshComponent->SetRelativeScale3D(FVector(worldToMeters, worldToMeters, worldToMeters));
}

View File

@@ -0,0 +1,46 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRSceneLatentActions.h"
#include "OculusXRHMD.h"
#include "OculusXRAnchorBPFunctionLibrary.h"
#include "OculusXRSceneModule.h"
#include "OculusXRSceneDelegates.h"
#include "OculusXRScene.h"
UOculusXRAsyncAction_LaunchCaptureFlow* UOculusXRAsyncAction_LaunchCaptureFlow::LaunchCaptureFlowAsync(const UObject* WorldContext)
{
UWorld* World = GEngine->GetWorldFromContextObject(WorldContext, EGetWorldErrorMode::ReturnNull);
if (!ensureAlwaysMsgf(IsValid(WorldContext), TEXT("World Context was not valid.")))
{
return nullptr;
}
auto NewAction = NewObject<UOculusXRAsyncAction_LaunchCaptureFlow>();
NewAction->RegisterWithGameInstance(World->GetGameInstance());
return NewAction;
}
void UOculusXRAsyncAction_LaunchCaptureFlow::Activate()
{
RequestId = 0;
FOculusXRSceneEventDelegates::OculusSceneCaptureComplete.AddUObject(this, &UOculusXRAsyncAction_LaunchCaptureFlow::OnCaptureFinish);
auto result = OculusXRScene::FOculusXRScene::RequestSceneCapture(RequestId);
if (!UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(result))
{
FOculusXRSceneEventDelegates::OculusSceneCaptureComplete.RemoveAll(this);
Failure.Broadcast();
}
}
void UOculusXRAsyncAction_LaunchCaptureFlow::OnCaptureFinish(FOculusXRUInt64 Id, bool bSuccess)
{
if (RequestId != Id.GetValue())
{
UE_LOG(LogOculusXRScene, Verbose, TEXT("Incoming request id (%llu) doesn't match expected request id (%llu). Ignoring request."), Id.GetValue(), RequestId);
return;
}
FOculusXRSceneEventDelegates::OculusSceneCaptureComplete.RemoveAll(this);
Success.Broadcast();
SetReadyToDestroy();
}

View File

@@ -0,0 +1,82 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#include "OculusXRSceneModule.h"
#if OCULUS_SCENE_SUPPORTED_PLATFORMS
#include "OculusXRHMDModule.h"
#include "OculusXRHMD.h"
#include "OculusXRSceneEventHandling.h"
#include "OculusXRSceneComponents.h"
DEFINE_LOG_CATEGORY(LogOculusXRScene);
#define LOCTEXT_NAMESPACE "OculusXRScene"
//-------------------------------------------------------------------------------------------------
// FOculusXRSceneModule
//-------------------------------------------------------------------------------------------------
void FOculusXRSceneModule::StartupModule()
{
SceneXR = MakeShareable(new XRScene::FSceneXR());
SceneXR->RegisterAsOpenXRExtension();
FCoreDelegates::OnPostEngineInit.AddRaw(this, &FOculusXRSceneModule::OnPostEngineInit);
auto anchorsModule = FModuleManager::GetModulePtr<IOculusXRAnchorsModule>("OculusXRAnchors");
anchorsModule->AddCreateAnchorComponentInterface(this);
}
void FOculusXRSceneModule::ShutdownModule()
{
auto anchorsModule = FModuleManager::GetModulePtr<IOculusXRAnchorsModule>("OculusXRAnchors");
anchorsModule->RemoveCreateAnchorComponentInterface(this);
}
void FOculusXRSceneModule::OnPostEngineInit()
{
if (IsRunningCommandlet())
{
return;
}
if (!GEngine)
{
return;
}
OculusXRHMD::FOculusXRHMD* HMD = OculusXRHMD::FOculusXRHMD::GetOculusXRHMD();
if (!HMD)
{
UE_LOG(LogOculusXRScene, Warning, TEXT("Unable to retrieve OculusXRHMD, cannot add event polling delegates."));
return;
}
HMD->AddEventPollingDelegate(OculusXRHMD::FOculusXRHMDEventPollingDelegate::CreateStatic(&OculusXRScene::FOculusXRSceneEventHandling::OnPollEvent));
}
UOculusXRBaseAnchorComponent* FOculusXRSceneModule::TryCreateAnchorComponent(uint64 AnchorHandle, EOculusXRSpaceComponentType Type, UObject* Outer)
{
switch (Type)
{
case EOculusXRSpaceComponentType::ScenePlane:
return UOculusXRBaseAnchorComponent::FromSpace<UOculusXRPlaneAnchorComponent>(AnchorHandle, Outer);
case EOculusXRSpaceComponentType::SceneVolume:
return UOculusXRBaseAnchorComponent::FromSpace<UOculusXRVolumeAnchorComponent>(AnchorHandle, Outer);
case EOculusXRSpaceComponentType::SemanticClassification:
return UOculusXRBaseAnchorComponent::FromSpace<UOculusXRSemanticClassificationAnchorComponent>(AnchorHandle, Outer);
case EOculusXRSpaceComponentType::RoomLayout:
return UOculusXRBaseAnchorComponent::FromSpace<UOculusXRRoomLayoutAnchorComponent>(AnchorHandle, Outer);
case EOculusXRSpaceComponentType::TriangleMesh:
return UOculusXRBaseAnchorComponent::FromSpace<UOculusXRTriangleMeshAnchorComponent>(AnchorHandle, Outer);
default:
return nullptr;
}
}
#endif // OCULUS_SCENE_SUPPORTED_PLATFORMS
IMPLEMENT_MODULE(FOculusXRSceneModule, OculusXRScene)
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,54 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "IOculusXRSceneModule.h"
#include "OculusXRAnchorsModule.h"
#include "openxr/OculusXRSceneXR.h"
#define LOCTEXT_NAMESPACE "OculusXRScene"
//-------------------------------------------------------------------------------------------------
// FOculusXRSceneModule
//-------------------------------------------------------------------------------------------------
DECLARE_LOG_CATEGORY_EXTERN(LogOculusXRScene, Log, All);
typedef TSharedPtr<XRScene::FSceneXR, ESPMode::ThreadSafe> FSceneXRPtr;
#if OCULUS_SCENE_SUPPORTED_PLATFORMS
class FOculusXRSceneModule : public IOculusXRSceneModule, public IOculusXRCreateAnchorComponent
{
public:
static inline FOculusXRSceneModule& Get()
{
return FModuleManager::LoadModuleChecked<FOculusXRSceneModule>("OculusXRScene");
}
virtual ~FOculusXRSceneModule() = default;
// IModuleInterface interface
virtual void StartupModule() override;
virtual void ShutdownModule() override;
void OnPostEngineInit();
// IOculusXRCreateAnchorComponent
virtual UOculusXRBaseAnchorComponent* TryCreateAnchorComponent(uint64 AnchorHandle, EOculusXRSpaceComponentType Type, UObject* Outer) override;
FSceneXRPtr GetXrScene() { return SceneXR; }
private:
FSceneXRPtr SceneXR;
};
#else // OCULUS_SCENE_SUPPORTED_PLATFORMS
class FOculusXRSceneModule : public FDefaultModuleImpl
{
};
#endif // OCULUS_SCENE_SUPPORTED_PLATFORMS
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,117 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRSceneSubsystem.h"
#include "OculusXRSceneTypes.h"
#include "OculusXRScene.h"
#include "IOculusXRSceneModule.h"
#include "OculusXRSceneDelegates.h"
#include "OculusXRAnchorBPFunctionLibrary.h"
#include "OculusXRHMD.h"
#include "OculusXRHMDRuntimeSettings.h"
#include "OculusXRSceneModule.h"
#include "OculusXRPassthroughModule.h"
#include "OculusXRPassthroughXR.h"
UOculusXRSceneSubsystem::UOculusXRSceneSubsystem()
: requestedVisibilityState_(EOculusXRBoundaryVisibility::NotSuppressed)
, bInitialized(false)
{
}
bool UOculusXRSceneSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
return GetDefault<UOculusXRHMDRuntimeSettings>()->bBoundaryVisibilitySupportEnabled && GetDefault<UOculusXRHMDRuntimeSettings>()->bInsightPassthroughEnabled;
}
void UOculusXRSceneSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
bool enabled = GetDefault<UOculusXRHMDRuntimeSettings>()->bBoundaryVisibilitySupportEnabled;
bool suppressed = GetDefault<UOculusXRHMDRuntimeSettings>()->bDefaultBoundaryVisibilitySuppressed;
// This is the desired state
bool isBoundaryNotSuppressed = enabled && !suppressed;
requestedVisibilityState_ = isBoundaryNotSuppressed ? EOculusXRBoundaryVisibility::NotSuppressed : EOculusXRBoundaryVisibility::Suppressed;
visChangedEventHandle_ = FOculusXRSceneEventDelegates::OculusBoundaryVisibilityChanged.AddUObject(this, &UOculusXRSceneSubsystem::OnBoundaryVisibilityChanged);
bInitialized = true;
}
void UOculusXRSceneSubsystem::Deinitialize()
{
FOculusXRSceneEventDelegates::OculusBoundaryVisibilityChanged.Remove(visChangedEventHandle_);
bInitialized = false;
}
ETickableTickType UOculusXRSceneSubsystem::GetTickableTickType() const
{
return IsTemplate() ? ETickableTickType::Never : FTickableGameObject::GetTickableTickType();
}
bool UOculusXRSceneSubsystem::IsAllowedToTick() const
{
return !IsTemplate() && bInitialized;
}
void UOculusXRSceneSubsystem::Tick(float DeltaTime)
{
UpdateBoundary();
}
EOculusXRBoundaryVisibility UOculusXRSceneSubsystem::GetBoundaryVisibility()
{
EOculusXRBoundaryVisibility boundaryVisibility = {};
OculusXRScene::FOculusXRScene::GetBoundaryVisibility(boundaryVisibility);
return boundaryVisibility;
}
EOculusXRBoundaryVisibility UOculusXRSceneSubsystem::GetRequestedBoundaryVisibility()
{
return requestedVisibilityState_;
}
void UOculusXRSceneSubsystem::SetRequestedBoundaryVisibility(EOculusXRBoundaryVisibility Visibility)
{
requestedVisibilityState_ = Visibility;
}
void UOculusXRSceneSubsystem::OnBoundaryVisibilityChanged(EOculusXRBoundaryVisibility visibility)
{
// Do nothing on event
}
void UOculusXRSceneSubsystem::UpdateBoundary()
{
// If the state is the same, skip
auto currentVisibilityState = GetBoundaryVisibility();
if (currentVisibilityState == requestedVisibilityState_)
{
return;
}
// Log only if the value != the requested state, else we pollute the log (per-frame call)
UE_LOG(LogOculusXRScene, Log, TEXT("GetBoundaryVisibility -- Visibility(%s)"), *UEnum::GetValueAsString(currentVisibilityState));
// TODO: This should probably be part of the passthrough API
const FName SystemName(TEXT("OpenXR"));
const bool IsOpenXR = GEngine->XRSystem.IsValid() && (GEngine->XRSystem->GetSystemName() == SystemName);
if (OculusXRHMD::FOculusXRHMD::GetOculusXRHMD() != nullptr)
{
// If passthrough is not enabled or initialized, skip
auto result = FOculusXRHMDModule::GetPluginWrapper().GetInsightPassthroughInitializationState();
bool passthroughInitializedOrPending = (result >= 0);
bool passthroughEnabled = GetDefault<UOculusXRHMDRuntimeSettings>()->bInsightPassthroughEnabled;
if (!passthroughEnabled || !passthroughInitializedOrPending)
{
return;
}
}
else if (IsOpenXR)
{
if (!FOculusXRPassthroughModule::Get().GetPassthroughExtensionPlugin().Pin()->IsPassthroughEnabled())
{
return;
}
}
OculusXRScene::FOculusXRScene::RequestBoundaryVisibility(requestedVisibilityState_);
}

View File

@@ -0,0 +1,551 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRSceneXR.h"
#include "OpenXRCore.h"
#include "OpenXRHMD.h"
#include "IOpenXRHMDModule.h"
#include "OpenXR/OculusXROpenXRUtilities.h"
#include "OculusXRSceneModule.h"
#include "OculusXRHMDPrivate.h"
#include "OculusXRSceneDelegates.h"
#include "OculusXRAnchorsUtil.h"
#define LOCTEXT_NAMESPACE "OculusXRScene"
namespace XRScene
{
PFN_xrGetSpaceBoundingBox2DFB xrGetSpaceBoundingBox2DFB = nullptr;
PFN_xrGetSpaceBoundingBox3DFB xrGetSpaceBoundingBox3DFB = nullptr;
PFN_xrGetSpaceBoundary2DFB xrGetSpaceBoundary2DFB = nullptr;
PFN_xrGetSpaceSemanticLabelsFB xrGetSpaceSemanticLabelsFB = nullptr;
PFN_xrRequestSceneCaptureFB xrRequestSceneCaptureFB = nullptr;
PFN_xrGetSpaceRoomLayoutFB xrGetSpaceRoomLayoutFB = nullptr;
PFN_xrGetSpaceTriangleMeshMETA xrGetSpaceTriangleMeshMETA = nullptr;
PFN_xrRequestBoundaryVisibilityMETA xrRequestBoundaryVisibilityMETA = nullptr;
FSceneXR::FSceneXR()
: bExtSceneEnabled(false)
, bExtSceneCaptureEnabled(false)
, bExtBoundaryVisibilityEnabled(false)
, bExtSpatialEntityMeshEnabled(false)
, LastBoundaryVisibility(XR_BOUNDARY_VISIBILITY_MAX_ENUM_META)
, OpenXRHMD(nullptr)
{
}
FSceneXR::~FSceneXR()
{
}
void FSceneXR::RegisterAsOpenXRExtension()
{
#if defined(WITH_OCULUS_BRANCH)
// Feature not enabled on Marketplace build. Currently only for the meta fork
RegisterOpenXRExtensionModularFeature();
#endif
}
bool FSceneXR::GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions)
{
OutExtensions.Add(XR_FB_SCENE_EXTENSION_NAME);
return true;
}
bool FSceneXR::GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions)
{
OutExtensions.Add(XR_FB_SCENE_CAPTURE_EXTENSION_NAME);
OutExtensions.Add(XR_META_SPATIAL_ENTITY_MESH_EXTENSION_NAME);
OutExtensions.Add(XR_META_BOUNDARY_VISIBILITY_EXTENSION_NAME);
return true;
}
const void* FSceneXR::OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext)
{
if (InModule != nullptr)
{
bExtSceneEnabled = InModule->IsExtensionEnabled(XR_FB_SCENE_EXTENSION_NAME);
bExtSceneCaptureEnabled = InModule->IsExtensionEnabled(XR_FB_SCENE_CAPTURE_EXTENSION_NAME);
bExtBoundaryVisibilityEnabled = InModule->IsExtensionEnabled(XR_META_BOUNDARY_VISIBILITY_EXTENSION_NAME);
bExtSpatialEntityMeshEnabled = InModule->IsExtensionEnabled(XR_META_SPATIAL_ENTITY_MESH_EXTENSION_NAME);
UE_LOG(LogOculusXRScene, Log, TEXT("[SCENE] Extensions available"));
UE_LOG(LogOculusXRScene, Log, TEXT(" Scene: %hs"), bExtSceneEnabled ? "ENABLED" : "DISABLED");
UE_LOG(LogOculusXRScene, Log, TEXT(" Scene Capture: %hs"), bExtSceneCaptureEnabled ? "ENABLED" : "DISABLED");
UE_LOG(LogOculusXRScene, Log, TEXT(" Boundary: %hs"), bExtBoundaryVisibilityEnabled ? "ENABLED" : "DISABLED");
UE_LOG(LogOculusXRScene, Log, TEXT(" Mesh: %hs"), bExtSpatialEntityMeshEnabled ? "ENABLED" : "DISABLED");
}
return InNext;
}
const void* FSceneXR::OnCreateSession(XrInstance InInstance, XrSystemId InSystem, const void* InNext)
{
InitOpenXRFunctions(InInstance);
OpenXRHMD = (FOpenXRHMD*)GEngine->XRSystem.Get();
return InNext;
}
void FSceneXR::OnDestroySession(XrSession InSession)
{
OpenXRHMD = nullptr;
}
void FSceneXR::OnEvent(XrSession InSession, const XrEventDataBaseHeader* InHeader)
{
if (OpenXRHMD == nullptr)
{
UE_LOG(LogOculusXRScene, Log, TEXT("[FSceneXR::OnEvent] Receieved event but no HMD was present."));
return;
}
switch (InHeader->type)
{
case XR_TYPE_EVENT_DATA_BOUNDARY_VISIBILITY_CHANGED_META:
{
if (IsBoundaryVisibilityExtensionSupported())
{
const XrEventDataBoundaryVisibilityChangedMETA* const event =
reinterpret_cast<const XrEventDataBoundaryVisibilityChangedMETA*>(InHeader);
UE_LOG(LogOculusXRScene, Verbose, TEXT("[FSceneXR::OnEvent] XrEventDataBoundaryVisibilityChangedMETA"));
UE_LOG(LogOculusXRScene, Verbose, TEXT(" Visibility: %hs"), (event->boundaryVisibility == XR_BOUNDARY_VISIBILITY_SUPPRESSED_META) ? "SUPPRESSED" : "NOT SUPPRESSED");
FOculusXRSceneEventDelegates::OculusBoundaryVisibilityChanged.Broadcast(event->boundaryVisibility == XR_BOUNDARY_VISIBILITY_SUPPRESSED_META ? EOculusXRBoundaryVisibility::Suppressed : EOculusXRBoundaryVisibility::NotSuppressed);
LastBoundaryVisibility = event->boundaryVisibility;
}
break;
}
case XR_TYPE_EVENT_DATA_SCENE_CAPTURE_COMPLETE_FB:
{
if (IsSceneCaptureExtensionSupported())
{
const XrEventDataSceneCaptureCompleteFB* const event =
reinterpret_cast<const XrEventDataSceneCaptureCompleteFB*>(InHeader);
UE_LOG(LogOculusXRScene, Verbose, TEXT("[FSceneXR::OnEvent] XrEventDataSceneCaptureCompleteFB"));
UE_LOG(LogOculusXRScene, Verbose, TEXT(" Result: d"), event->result);
FOculusXRSceneEventDelegates::OculusSceneCaptureComplete.Broadcast(event->result, XR_SUCCEEDED(event->result));
}
break;
}
}
}
XrResult FSceneXR::GetScenePlane(uint64 AnchorHandle, FVector& OutPos, FVector& OutSize)
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetScenePlane] XR state is invalid."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsSceneExtensionSupported())
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetScenePlane] Scene extension is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
XrRect2Df rect;
auto result = xrGetSpaceBoundingBox2DFB(OpenXRHMD->GetSession(), (XrSpace)AnchorHandle, &rect);
if (XR_FAILED(result))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetScenePlane] Get space bounding box 2D failed. Result: %d"), result);
return result;
}
// Convert to UE's coordinates system
OutPos.X = 0;
OutPos.Y = rect.offset.x;
OutPos.Z = rect.offset.y;
OutSize.X = 0;
OutSize.Y = rect.extent.width;
OutSize.Z = rect.extent.height;
return result;
}
XrResult FSceneXR::GetSceneVolume(uint64 AnchorHandle, FVector& OutPos, FVector& OutSize)
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetSceneVolume] XR state is invalid."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsSceneExtensionSupported())
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetSceneVolume] Scene extension is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
XrRect3DfFB rect;
auto result = xrGetSpaceBoundingBox3DFB(OpenXRHMD->GetSession(), (XrSpace)AnchorHandle, &rect);
if (XR_FAILED(result))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetSceneVolume] Get space bounding box 3D failed. Result: %d"), result);
return result;
}
// Convert from OpenXR's right-handed to Unreal's left-handed coordinate system.
// OpenXR Unreal
// | y | z
// | |
// z <----+ +----> x
// / /
// x/ y/
//
OutPos.X = -rect.offset.z;
OutPos.Y = rect.offset.x;
OutPos.Z = rect.offset.y;
// The position represents the corner of the volume which has the lowest value
// of each axis. Since we flipped the sign of one of the axes we need to adjust
// the position to the other side of the volume
OutPos.X -= rect.extent.depth;
// We keep the size positive for all dimensions
OutSize.X = rect.extent.depth;
OutSize.Y = rect.extent.width;
OutSize.Z = rect.extent.height;
return result;
}
XrResult FSceneXR::GetBoundary2D(uint64 AnchorHandle, TArray<FVector2f>& OutVertices)
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetBoundary2D] XR state is invalid."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsSceneExtensionSupported())
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetBoundary2D] Scene extension is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
XrBoundary2DFB boundary{ XR_TYPE_BOUNDARY_2D_FB, nullptr };
boundary.vertexCapacityInput = 0;
boundary.vertexCountOutput = 0;
boundary.vertices = nullptr;
auto getCountResult = xrGetSpaceBoundary2DFB(OpenXRHMD->GetSession(), (XrSpace)AnchorHandle, &boundary);
if (XR_FAILED(getCountResult))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetBoundary2D] Get space boundary 2D vertex count failed. Result: %d"), getCountResult);
return getCountResult;
}
TArray<XrVector2f> vertices;
vertices.SetNum(boundary.vertexCountOutput);
boundary.vertexCapacityInput = boundary.vertexCountOutput;
boundary.vertices = vertices.GetData();
auto getVerticesResult = xrGetSpaceBoundary2DFB(OpenXRHMD->GetSession(), (XrSpace)AnchorHandle, &boundary);
if (XR_FAILED(getVerticesResult))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetBoundary2D] Get space boundary 2D vertices failed. Result: %d"), getVerticesResult);
return getVerticesResult;
}
OutVertices.Reserve(vertices.Num());
for (auto& it : vertices)
{
OutVertices.Add(FVector2f(it.x, it.y));
}
return getVerticesResult;
}
XrResult FSceneXR::GetSemanticClassification(uint64 AnchorHandle, TArray<FString>& OutSemanticClassifications)
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetSemanticClassification] XR state is invalid."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsSceneExtensionSupported())
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetSemanticClassification] Scene extension is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
static const char* recognizedLabels = "DESK,COUCH,FLOOR,CEILING,WALL_FACE,WINDOW_FRAME,DOOR_FRAME,STORAGE,BED,SCREEN,LAMP,PLANT,OTHER,TABLE,WALL_ART,INVISIBLE_WALL_FACE,GLOBAL_MESH"
;
const XrSemanticLabelsSupportInfoFB semanticLabelsSupportInfo = {
XR_TYPE_SEMANTIC_LABELS_SUPPORT_INFO_FB,
nullptr,
XR_SEMANTIC_LABELS_SUPPORT_ACCEPT_DESK_TO_TABLE_MIGRATION_BIT_FB | XR_SEMANTIC_LABELS_SUPPORT_ACCEPT_INVISIBLE_WALL_FACE_BIT_FB,
recognizedLabels
};
XrSemanticLabelsFB xrLabels{ XR_TYPE_SEMANTIC_LABELS_FB, &semanticLabelsSupportInfo };
xrLabels.bufferCountOutput = 0;
xrLabels.bufferCapacityInput = 0;
xrLabels.buffer = nullptr;
XrResult result = xrGetSpaceSemanticLabelsFB(OpenXRHMD->GetSession(), (XrSpace)AnchorHandle, &xrLabels);
if (XR_FAILED(result))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetSemanticClassification] Get semantic label buffer size failed. Result: %d"), result);
return result;
}
TArray<char> buffer;
buffer.SetNum(xrLabels.bufferCountOutput);
xrLabels.bufferCapacityInput = xrLabels.bufferCountOutput;
xrLabels.buffer = buffer.GetData();
result = xrGetSpaceSemanticLabelsFB(OpenXRHMD->GetSession(), (XrSpace)AnchorHandle, &xrLabels);
if (XR_FAILED(result))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetSemanticClassification] Get semantic label buffer failed. Result: %d"), result);
return result;
}
FString labelsStr(xrLabels.bufferCountOutput, xrLabels.buffer);
labelsStr.ParseIntoArray(OutSemanticClassifications, TEXT(","));
return result;
}
XrResult FSceneXR::RequestSceneCapture(uint64& OutRequestID)
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[RequestSceneCapture] XR state is invalid."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsSceneCaptureExtensionSupported())
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[RequestSceneCapture] Scene capture extension is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
XrSceneCaptureRequestInfoFB info{ XR_TYPE_SCENE_CAPTURE_REQUEST_INFO_FB, nullptr };
info.request = nullptr;
info.requestByteCount = 0;
auto result = xrRequestSceneCaptureFB(OpenXRHMD->GetSession(), &info, (XrAsyncRequestIdFB*)&OutRequestID);
if (XR_FAILED(result))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[RequestSceneCapture] Get scene capture failed. Result: %d"), result);
}
UE_LOG(LogOculusXRScene, Log, TEXT("[RequestSceneCapture] Started scene capture: RequestID (%llu)"), OutRequestID);
return result;
}
XrResult FSceneXR::GetRoomLayout(uint64 AnchorHandle, const uint32 MaxWallsCapacity, FOculusXRUUID& OutCeilingUuid, FOculusXRUUID& OutFloorUuid, TArray<FOculusXRUUID>& OutWallsUuid)
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetRoomLayout] XR state is invalid."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsSceneExtensionSupported())
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetRoomLayout] Scene extension is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
XrRoomLayoutFB roomLayout{ XR_TYPE_ROOM_LAYOUT_FB, nullptr };
roomLayout.wallUuidCapacityInput = 0;
roomLayout.wallUuidCountOutput = 0;
roomLayout.wallUuids = nullptr;
auto getWallsResult = xrGetSpaceRoomLayoutFB(OpenXRHMD->GetSession(), (XrSpace)AnchorHandle, &roomLayout);
if (XR_FAILED(getWallsResult))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetRoomLayout] Failed to get wall count. Result: %d"), getWallsResult);
return getWallsResult;
}
TArray<XrUuidEXT> wallUuids;
wallUuids.SetNum(roomLayout.wallUuidCountOutput);
roomLayout.wallUuidCapacityInput = roomLayout.wallUuidCountOutput;
roomLayout.wallUuids = wallUuids.GetData();
auto getDataResult = xrGetSpaceRoomLayoutFB(OpenXRHMD->GetSession(), (XrSpace)AnchorHandle, &roomLayout);
if (XR_FAILED(getDataResult))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetRoomLayout] Failed to get room layout. Result: %d"), getDataResult);
return getDataResult;
}
OutCeilingUuid = FOculusXRUUID(roomLayout.ceilingUuid.data);
OutFloorUuid = FOculusXRUUID(roomLayout.floorUuid.data);
for (auto& it : wallUuids)
{
OutWallsUuid.Add(it.data);
}
return getDataResult;
}
XrResult FSceneXR::GetTriangleMesh(uint64 AnchorHandle, TArray<FVector>& Vertices, TArray<int32>& Triangles)
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetTriangleMesh] XR state is invalid."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsSpatialEntityMeshExtensionSupported())
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetTriangleMesh] Spatial entity mesh extension is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
const XrSpaceTriangleMeshGetInfoMETA xrGetInfo{ XR_TYPE_SPACE_TRIANGLE_MESH_GET_INFO_META };
XrSpaceTriangleMeshMETA xrTriangleMesh{ XR_TYPE_SPACE_TRIANGLE_MESH_META, nullptr };
xrTriangleMesh.indexCapacityInput = 0;
xrTriangleMesh.indexCountOutput = 0;
xrTriangleMesh.indices = nullptr;
xrTriangleMesh.vertexCapacityInput = 0;
xrTriangleMesh.vertexCountOutput = 0;
xrTriangleMesh.vertices = nullptr;
auto getMeshCountsResult = xrGetSpaceTriangleMeshMETA((XrSpace)AnchorHandle, &xrGetInfo, &xrTriangleMesh);
if (XR_FAILED(getMeshCountsResult))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetTriangleMesh] Failed to get vertex and index count. Result: %d"), getMeshCountsResult);
return getMeshCountsResult;
}
TArray<uint32_t> indices;
indices.SetNum(xrTriangleMesh.indexCountOutput);
xrTriangleMesh.indexCapacityInput = xrTriangleMesh.indexCountOutput;
xrTriangleMesh.indices = indices.GetData();
TArray<XrVector3f> vertices;
vertices.SetNum(xrTriangleMesh.vertexCountOutput);
xrTriangleMesh.vertexCapacityInput = xrTriangleMesh.vertexCountOutput;
xrTriangleMesh.vertices = vertices.GetData();
auto getMeshDataResult = xrGetSpaceTriangleMeshMETA((XrSpace)AnchorHandle, &xrGetInfo, &xrTriangleMesh);
if (XR_FAILED(getMeshDataResult))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetTriangleMesh] Failed to get vertex and index data. Result: %d"), getMeshDataResult);
return getMeshDataResult;
}
for (auto& it : indices)
{
Triangles.Add(it);
}
for (auto& it : vertices)
{
Vertices.Add(ToFVector(it));
}
return getMeshDataResult;
}
XrResult FSceneXR::RequestBoundaryVisibility(EOculusXRBoundaryVisibility NewVisibilityRequest)
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[RequestBoundaryVisibility] XR state is invalid."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsBoundaryVisibilityExtensionSupported())
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[RequestBoundaryVisibility] Boundary visibility extension is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
XrSceneCaptureRequestInfoFB info{ XR_TYPE_SCENE_CAPTURE_REQUEST_INFO_FB, nullptr };
info.request = nullptr;
info.requestByteCount = 0;
XrBoundaryVisibilityMETA visibility;
switch (NewVisibilityRequest)
{
case EOculusXRBoundaryVisibility::NotSuppressed:
visibility = XR_BOUNDARY_VISIBILITY_NOT_SUPPRESSED_META;
break;
case EOculusXRBoundaryVisibility::Suppressed:
visibility = XR_BOUNDARY_VISIBILITY_SUPPRESSED_META;
break;
default:
visibility = XR_BOUNDARY_VISIBILITY_MAX_ENUM_META;
}
auto result = xrRequestBoundaryVisibilityMETA(OpenXRHMD->GetSession(), visibility);
if (XR_FAILED(result))
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[RequestBoundaryVisibility] Get boundary visibility failed. Result: %d"), result);
}
return result;
}
XrResult FSceneXR::GetBoundaryVisibility(EOculusXRBoundaryVisibility& OutVisibility)
{
if (!OpenXRHMD || !OpenXRHMD->GetInstance() || !OpenXRHMD->GetSession())
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetBoundaryVisibility] XR state is invalid."));
return XR_ERROR_VALIDATION_FAILURE;
}
if (!IsBoundaryVisibilityExtensionSupported())
{
UE_LOG(LogOculusXRScene, Warning, TEXT("[GetBoundaryVisibility] Boundary visibility extension is unsupported."));
return XR_ERROR_VALIDATION_FAILURE;
}
OutVisibility = (LastBoundaryVisibility == XR_BOUNDARY_VISIBILITY_SUPPRESSED_META)
? EOculusXRBoundaryVisibility::Suppressed
: EOculusXRBoundaryVisibility::NotSuppressed;
return XR_SUCCESS;
}
void FSceneXR::InitOpenXRFunctions(XrInstance InInstance)
{
// XR_FB_scene
if (IsSceneExtensionSupported())
{
OculusXR::XRGetInstanceProcAddr(InInstance, "xrGetSpaceBoundingBox2DFB", &xrGetSpaceBoundingBox2DFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrGetSpaceBoundingBox3DFB", &xrGetSpaceBoundingBox3DFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrGetSpaceBoundary2DFB", &xrGetSpaceBoundary2DFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrGetSpaceSemanticLabelsFB", &xrGetSpaceSemanticLabelsFB);
OculusXR::XRGetInstanceProcAddr(InInstance, "xrGetSpaceRoomLayoutFB", &xrGetSpaceRoomLayoutFB);
}
// XR_FB_scene_capture
if (IsSceneCaptureExtensionSupported())
{
OculusXR::XRGetInstanceProcAddr(InInstance, "xrRequestSceneCaptureFB", &xrRequestSceneCaptureFB);
}
// XR_META_spatial_entity_mesh
if (IsSpatialEntityMeshExtensionSupported())
{
OculusXR::XRGetInstanceProcAddr(InInstance, "xrGetSpaceTriangleMeshMETA", &xrGetSpaceTriangleMeshMETA);
}
// XR_META_boundary_visibility
if (IsBoundaryVisibilityExtensionSupported())
{
OculusXR::XRGetInstanceProcAddr(InInstance, "xrRequestBoundaryVisibilityMETA", &xrRequestBoundaryVisibilityMETA);
}
}
} // namespace XRScene
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,72 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "OculusXRSceneXRIncludes.h"
#include "IOpenXRExtensionPlugin.h"
#include "OculusXRSceneTypes.h"
#include "OculusXRAnchorTypes.h"
#define LOCTEXT_NAMESPACE "OculusXRScene"
class FOpenXRHMD;
namespace XRScene
{
extern PFN_xrGetSpaceBoundingBox2DFB xrGetSpaceBoundingBox2DFB;
extern PFN_xrGetSpaceBoundingBox3DFB xrGetSpaceBoundingBox3DFB;
extern PFN_xrGetSpaceBoundary2DFB xrGetSpaceBoundary2DFB;
extern PFN_xrGetSpaceSemanticLabelsFB xrGetSpaceSemanticLabelsFB;
extern PFN_xrRequestSceneCaptureFB xrRequestSceneCaptureFB;
extern PFN_xrGetSpaceRoomLayoutFB xrGetSpaceRoomLayoutFB;
extern PFN_xrGetSpaceTriangleMeshMETA xrGetSpaceTriangleMeshMETA;
extern PFN_xrRequestBoundaryVisibilityMETA xrRequestBoundaryVisibilityMETA;
class FSceneXR : public IOpenXRExtensionPlugin
{
public:
// IOculusXROpenXRHMDPlugin
virtual bool GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
virtual bool GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
virtual const void* OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext) override;
virtual const void* OnCreateSession(XrInstance InInstance, XrSystemId InSystem, const void* InNext) override;
virtual void OnDestroySession(XrSession InSession) override;
virtual void OnEvent(XrSession InSession, const XrEventDataBaseHeader* InHeader) override;
public:
FSceneXR();
virtual ~FSceneXR();
void RegisterAsOpenXRExtension();
bool IsSceneExtensionSupported() const { return bExtSceneEnabled; }
bool IsSceneCaptureExtensionSupported() const { return bExtSceneCaptureEnabled; }
bool IsBoundaryVisibilityExtensionSupported() const { return bExtBoundaryVisibilityEnabled; }
bool IsSpatialEntityMeshExtensionSupported() const { return bExtSpatialEntityMeshEnabled; }
XrResult GetScenePlane(uint64 AnchorHandle, FVector& OutPos, FVector& OutSize);
XrResult GetSceneVolume(uint64 AnchorHandle, FVector& OutPos, FVector& OutSize);
XrResult GetBoundary2D(uint64 AnchorHandle, TArray<FVector2f>& OutVertices);
XrResult GetSemanticClassification(uint64 AnchorHandle, TArray<FString>& OutSemanticClassifications);
XrResult RequestSceneCapture(uint64& OutRequestID);
XrResult GetRoomLayout(uint64 AnchorHandle, const uint32 MaxWallsCapacity, FOculusXRUUID& OutCeilingUuid, FOculusXRUUID& OutFloorUuid, TArray<FOculusXRUUID>& OutWallsUuid);
XrResult GetTriangleMesh(uint64 AnchorHandle, TArray<FVector>& Vertices, TArray<int32>& Triangles);
XrResult RequestBoundaryVisibility(EOculusXRBoundaryVisibility NewVisibilityRequest);
XrResult GetBoundaryVisibility(EOculusXRBoundaryVisibility& OutVisibility);
private:
void InitOpenXRFunctions(XrInstance InInstance);
bool bExtSceneEnabled;
bool bExtSceneCaptureEnabled;
bool bExtBoundaryVisibilityEnabled;
bool bExtSpatialEntityMeshEnabled;
XrBoundaryVisibilityMETA LastBoundaryVisibility;
FOpenXRHMD* OpenXRHMD;
};
} // namespace XRScene
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,7 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include <khronos/openxr/openxr.h>
#include <khronos/openxr/meta_openxr_preview/meta_boundary_visibility.h>
#include "openxr/OculusXRAnchorsXRIncludes.h"