Started the project... finally...

Started the project. This right now only includes Meta XR and Android setup with VR template. More improvements to come!
This commit is contained in:
2024-05-06 20:03:14 +03:00
parent 372d02a195
commit e55214a398
1201 changed files with 181249 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
namespace UnrealBuildTool.Rules
{
public class OculusXRScene : ModuleRules
{
public OculusXRScene(ReadOnlyTargetRules Target) : base(Target)
{
bUseUnity = true;
PrivateDependencyModuleNames.AddRange(
new string[]
{
"Core",
"CoreUObject",
"Engine",
"OculusXRHMD",
"OculusXRAnchors",
"OVRPluginXR",
"ProceduralMeshComponent",
});
PrivateIncludePaths.AddRange(
new string[] {
// Relative to Engine\Plugins\Runtime\Oculus\OculusVR\Source
"OculusXRHMD/Private",
"OculusXRAnchors/Private"
});
PublicIncludePaths.AddRange(
new string[] {
"Runtime/Engine/Classes/Components",
});
}
}
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRScene.h"
#include "OculusXRSceneModule.h"
#include "OculusXRHMDPrivate.h"
#include "OculusXRHMD.h"
#include "OculusXRPluginWrapper.h"
#define LOCTEXT_NAMESPACE "OculusXRScene"
namespace OculusXRScene
{
} // namespace OculusXRScene
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,757 @@
// @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 "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()
{
FOculusXRSpaceQueryInfo queryInfo;
queryInfo.MaxQuerySpaces = MaxQueries;
queryInfo.FilterType = EOculusXRSpaceQueryFilterType::FilterByComponentType;
queryInfo.ComponentFilter.Add(EOculusXRSpaceComponentType::RoomLayout);
EOculusXRAnchorResult::Type anchorQueryResult;
OculusXRAnchors::FOculusXRAnchors::QueryAnchorsAdvanced(queryInfo,
FOculusXRAnchorQueryDelegate::CreateUObject(this, &AOculusXRSceneActor::RoomLayoutQueryComplete), anchorQueryResult);
return anchorQueryResult;
}
void AOculusXRSceneActor::RoomLayoutQueryComplete(EOculusXRAnchorResult::Type AnchorResult, const TArray<FOculusXRSpaceQueryResult>& QueryResults)
{
UE_LOG(LogOculusXRScene, Verbose, TEXT("RoomLayoutQueryComplete (Result = %d)"), AnchorResult);
for (auto& QueryElement : QueryResults)
{
UE_LOG(LogOculusXRScene, Verbose, TEXT("RoomLayoutQueryComplete -- Query Element (space = %llu, uuid = %s"), QueryElement.Space.Value, *BytesToHex(QueryElement.UUID.UUIDBytes, OCULUSXR_UUID_SIZE));
FOculusXRRoomLayout roomLayout;
const bool bGetRoomLayoutResult = RoomLayoutManagerComponent->GetRoomLayout(QueryElement.Space.Value, roomLayout, MaxQueries);
if (!bGetRoomLayoutResult)
{
UE_LOG(LogOculusXRScene, Error, TEXT("RoomLayoutQueryComplete -- Failed to get room layout for space (space = %llu, uuid = %s"),
QueryElement.Space.Value, *BytesToHex(QueryElement.UUID.UUIDBytes, OCULUSXR_UUID_SIZE));
continue;
}
roomLayout.RoomAnchorHandle = QueryElement.Space;
roomLayout.RoomUuid = QueryElement.UUID;
// If we're only loading the active room we start that floor check query here, otherwise do the room query
if (bActiveRoomOnly)
{
QueryFloorForActiveRoom(QueryElement.Space, roomLayout);
}
else
{
StartSingleRoomQuery(QueryElement.Space, roomLayout);
}
}
}
EOculusXRAnchorResult::Type AOculusXRSceneActor::QueryRoomUUIDs(const FOculusXRUInt64 RoomSpaceID, const TArray<FOculusXRUUID>& RoomUUIDs)
{
EOculusXRAnchorResult::Type startAnchorQueryResult;
OculusXRAnchors::FOculusXRAnchors::QueryAnchors(
RoomUUIDs,
EOculusXRSpaceStorageLocation::Local,
FOculusXRAnchorQueryDelegate::CreateUObject(this, &AOculusXRSceneActor::SceneRoomQueryComplete, RoomSpaceID),
startAnchorQueryResult);
return startAnchorQueryResult;
}
void AOculusXRSceneActor::SceneRoomQueryComplete(EOculusXRAnchorResult::Type AnchorResult, const TArray<FOculusXRSpaceQueryResult>& QueryResults, const FOculusXRUInt64 RoomSpaceID)
{
if (!UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(AnchorResult))
{
return;
}
bool bOutPending = false;
for (auto& AnchorQueryElement : QueryResults)
{
if (SceneGlobalMeshComponent)
{
TArray<FString> semanticClassifications;
GetSemanticClassifications(AnchorQueryElement.Space.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::GetSpaceComponentStatus(
AnchorQueryElement.Space.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"));
continue;
}
UClass* sceneAnchorComponentInstanceClass = SceneGlobalMeshComponent->GetAnchorComponentClass();
AActor* globalMeshAnchor = SpawnActorWithSceneComponent(AnchorQueryElement.Space.Value, RoomSpaceID, semanticClassifications, sceneAnchorComponentInstanceClass);
SceneGlobalMeshComponent->CreateMeshComponent(AnchorQueryElement.Space, globalMeshAnchor, RoomLayoutManagerComponent);
continue;
}
}
bool bIsScenePlane = false;
bool bIsSceneVolume = false;
EOculusXRAnchorResult::Type isPlaneResult = OculusXRAnchors::FOculusXRAnchorManager::GetSpaceComponentStatus(
AnchorQueryElement.Space.Value, EOculusXRSpaceComponentType::ScenePlane, bIsScenePlane, bOutPending);
EOculusXRAnchorResult::Type isVolumeResult = OculusXRAnchors::FOculusXRAnchorManager::GetSpaceComponentStatus(
AnchorQueryElement.Space.Value, EOculusXRSpaceComponentType::SceneVolume, bIsSceneVolume, bOutPending);
bool bIsPlaneResultSuccess = UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(isPlaneResult);
bool bIsVolumeResultSuccess = UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(isVolumeResult);
AActor* anchor = nullptr;
if (bIsPlaneResultSuccess && bIsScenePlane)
{
EOculusXRAnchorResult::Type Result;
FVector scenePlanePos;
FVector scenePlaneSize;
bool ResultSuccess = OculusXRAnchors::FOculusXRAnchors::GetSpaceScenePlane(AnchorQueryElement.Space.Value, scenePlanePos, scenePlaneSize, Result);
if (ResultSuccess)
{
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(AnchorQueryElement.Space.Value, semanticClassifications);
UE_LOG(LogOculusXRScene, Log, TEXT("SpatialAnchor ScenePlane label is %s"), semanticClassifications.Num() > 0 ? *semanticClassifications[0] : TEXT("unknown"));
anchor = SpawnOrUpdateSceneAnchor(anchor, AnchorQueryElement.Space, 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)
{
EOculusXRAnchorResult::Type Result;
FVector sceneVolumePos;
FVector sceneVolumeSize;
bool ResultSuccess = OculusXRAnchors::FOculusXRAnchors::GetSpaceSceneVolume(AnchorQueryElement.Space.Value, sceneVolumePos, sceneVolumeSize, Result);
if (ResultSuccess)
{
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(AnchorQueryElement.Space.Value, semanticClassifications);
UE_LOG(LogOculusXRScene, Log, TEXT("SpatialAnchor SceneVolume label is %s"), semanticClassifications.Num() > 0 ? *semanticClassifications[0] : TEXT("unknown"));
anchor = SpawnOrUpdateSceneAnchor(anchor, AnchorQueryElement.Space, 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;
OculusXRAnchors::FOculusXRAnchors::QueryAnchors(
TArray<FOculusXRUUID>({ RoomLayout.FloorUuid }),
EOculusXRSpaceStorageLocation::Local,
FOculusXRAnchorQueryDelegate::CreateUObject(this, &AOculusXRSceneActor::ActiveRoomFloorQueryComplete, RoomSpaceID, RoomLayout),
anchorQueryResult);
return anchorQueryResult;
}
void AOculusXRSceneActor::ActiveRoomFloorQueryComplete(EOculusXRAnchorResult::Type AnchorResult, const TArray<FOculusXRSpaceQueryResult>& QueryResults, FOculusXRUInt64 RoomSpaceID, FOculusXRRoomLayout RoomLayout)
{
if (QueryResults.Num() != 1)
{
UE_LOG(LogOculusXRScene, Error, TEXT("Wrong number of elements returned from query for floor UUID. Result count (%d), UUID (%s), Room Space ID (%llu)"), QueryResults.Num(), *RoomLayout.FloorUuid.ToString(), RoomSpaceID.Value);
return;
}
const FOculusXRSpaceQueryResult& floorQueryResult = QueryResults[0];
TArray<FString> semanticClassifications;
GetSemanticClassifications(floorQueryResult.Space.Value, 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)"), *RoomLayout.FloorUuid.ToString(), RoomSpaceID.Value);
return;
}
EOculusXRAnchorResult::Type getBoundaryResult;
TArray<FVector2f> boundaryVertices;
if (!OculusXRAnchors::FOculusXRAnchors::GetSpaceBoundary2D(floorQueryResult.Space.Value, boundaryVertices, getBoundaryResult))
{
UE_LOG(LogOculusXRScene, Error, TEXT("Failed to get space boundary vertices for floor. UUID (%s), Room Space ID (%llu)"), *RoomLayout.FloorUuid.ToString(), RoomSpaceID.Value);
return;
}
OculusXRHMD::FOculusXRHMD* HMD = OculusXRHMD::FOculusXRHMD::GetOculusXRHMD();
check(HMD);
OculusXRHMD::FPose headPose;
HMD->GetCurrentPose(OculusXRHMD::ToExternalDeviceId(ovrpNode_Head), headPose.Orientation, headPose.Position);
TArray<FVector> convertedBoundaryPoints;
FTransform floorTransform;
if (!UOculusXRAnchorBPFunctionLibrary::GetAnchorTransformByHandle(floorQueryResult.Space, floorTransform))
{
UE_LOG(LogOculusXRScene, Error, TEXT("Failed to get the floor anchor transform. Floor Space ID (%llu)"), floorQueryResult.Space.Value);
return;
}
// Convert the boundary vertices to game engine world space
for (auto& it : boundaryVertices)
{
FVector pos = floorTransform.TransformPosition(FVector(0, it.X * HMD->GetWorldToMetersScale(), it.Y * HMD->GetWorldToMetersScale()));
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(headPose.Position.X, headPose.Position.Y), new2DBoundary))
{
UE_LOG(LogOculusXRScene, Verbose, TEXT("Floor failed active room check. UUID (%s), Room Space ID (%llu)"), *RoomLayout.FloorUuid.ToString(), RoomSpaceID.Value);
return;
}
StartSingleRoomQuery(RoomSpaceID, RoomLayout);
}
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;
bool Result = OculusXRAnchors::FOculusXRAnchors::GetSpaceSemanticClassification(Space, OutSemanticLabels, SemanticLabelAnchorResult);
if (Result)
{
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,5 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRSceneDelegates.h"

View File

@@ -0,0 +1,38 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRSceneEventHandling.h"
#include "OculusXRHMD.h"
#include "IOculusXRSceneModule.h"
#include "OculusXRSceneDelegates.h"
#include "OculusXRSceneEventDelegates.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_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,11 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "OculusXRSceneFunctionLibrary.h"
#include "OculusXRAnchorBPFunctionLibrary.h"
#include "OculusXRScene.h"
#include "OculusXRSceneSubsystem.h"
#include "OculusXRHMDPrivate.h"
#include "OculusXRHMD.h"

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,44 @@
// @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"
DEFINE_LOG_CATEGORY(LogOculusXRScene);
#define LOCTEXT_NAMESPACE "OculusXRScene"
//-------------------------------------------------------------------------------------------------
// FOculusXRSceneModule
//-------------------------------------------------------------------------------------------------
void FOculusXRSceneModule::StartupModule()
{
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));
}
void FOculusXRSceneModule::ShutdownModule()
{
}
#endif // OCULUS_SCENE_SUPPORTED_PLATFORMS
IMPLEMENT_MODULE(FOculusXRSceneModule, OculusXRScene)
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,37 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "IOculusXRSceneModule.h"
#define LOCTEXT_NAMESPACE "OculusXRScene"
//-------------------------------------------------------------------------------------------------
// FOculusXRSceneModule
//-------------------------------------------------------------------------------------------------
#if OCULUS_SCENE_SUPPORTED_PLATFORMS
DECLARE_LOG_CATEGORY_EXTERN(LogOculusXRScene, Log, All);
class FOculusXRSceneModule : public IOculusXRSceneModule
{
public:
virtual ~FOculusXRSceneModule() = default;
// IModuleInterface interface
virtual void StartupModule() override;
virtual void ShutdownModule() override;
private:
};
#else // OCULUS_SCENE_SUPPORTED_PLATFORMS
class FOculusXRSceneModule : public FDefaultModuleImpl
{
};
#endif // OCULUS_SCENE_SUPPORTED_PLATFORMS
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,43 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "OculusXRSceneSubsystem.h"
#include "OculusXRSceneTypes.h"
#include "OculusXRScene.h"
#include "OculusXRSceneDelegates.h"
#include "OculusXRAnchorBPFunctionLibrary.h"
#include "OculusXRHMD.h"
#include "OculusXRHMDRuntimeSettings.h"
UOculusXRSceneSubsystem::UOculusXRSceneSubsystem()
{
}
bool UOculusXRSceneSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
return false;
}
void UOculusXRSceneSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
}
void UOculusXRSceneSubsystem::Deinitialize()
{
}
ETickableTickType UOculusXRSceneSubsystem::GetTickableTickType() const
{
return IsTemplate() ? ETickableTickType::Never : FTickableGameObject::GetTickableTickType();
}
bool UOculusXRSceneSubsystem::IsAllowedToTick() const
{
return false;
}
void UOculusXRSceneSubsystem::Tick(float DeltaTime)
{
}

View File

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

View File

@@ -0,0 +1,14 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "OculusXRAnchorTypes.h"
#include "OculusXRSceneTypes.h"
namespace OculusXRScene
{
struct OCULUSXRSCENE_API FOculusXRScene
{
};
} // namespace OculusXRScene

View File

@@ -0,0 +1,178 @@
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "GameFramework/Actor.h"
#include "OculusXRRoomLayoutManagerComponent.h"
#include "OculusXRAnchorComponent.h"
#include "OculusXRFunctionLibrary.h"
#include "OculusXRSceneAnchorComponent.h"
#include "OculusXRSceneTypes.h"
#include "OculusXRSceneActor.generated.h"
/** EOculusXRLaunchCaptureFlowWhenMissingScene
* Used to dictate whether the actor should launch the Capture Flow application when a scene is not detected on the device.
* The Actor will check if a scene capture is either non-existent or invalid (ie. missing walls/ceiling/floor) before checking if Capture Flow
* should be launched.
*
* NEVER: will never launch Flow Capture.
* ONCE: will only launch it once. If the actor still doesn't detect that a scene was captured, it will not launch Capture Flow again.
* ALWAYS: will always re-launch Flow Capture if a scene was not detected on the device.
*/
UENUM(BlueprintType)
enum EOculusXRLaunchCaptureFlowWhenMissingScene
{
NEVER UMETA(DisplayName = "Never"),
ONCE UMETA(DisplayName = "Once"),
ALWAYS UMETA(DisplayName = "Always")
};
/** FOculusXRSpawnedSceneAnchorProperties
* Properties/Components that a spawned scene anchor will use.
*/
USTRUCT(BlueprintType)
struct FOculusXRSpawnedSceneAnchorProperties
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, Category = "Spawned Scene Anchor Properties")
TSoftClassPtr<UOculusXRSceneAnchorComponent> ActorComponent = TSoftClassPtr<UOculusXRSceneAnchorComponent>(FSoftClassPath(UOculusXRSceneAnchorComponent::StaticClass()));
UPROPERTY(EditAnywhere, Category = "Spawned Scene Anchor Properties")
TSoftObjectPtr<UStaticMesh> StaticMesh;
UPROPERTY(EditAnywhere, Category = "Spawned Scene Anchor Properties", Meta = (DeprecatedProperty, DeprecationMessage = "This property is deprecated. Alignment is done automatically at lower level."))
bool ForceParallelToFloor = false;
UPROPERTY(EditAnywhere, Category = "Spawned Scene Anchor Properties")
FVector AddOffset = FVector::ZeroVector;
};
/**
* AOculusXRSceneActor
* The purpose of this actor is to be able to spawn "scene anchor" actors.
*
* Each actor type (based on their semantic label) can be configured to be spawned with a specific mesh and actor component.
*
* Overall, it provides a simple interface to be able to quickly get a captured scene from Capture Flow populated at runtime.
* It also provides a basic and flexible template to making use of the OculusAnchorSDK and UOculusXRRoomLayoutManagerComponent
* to drive the actor's logic. This removes the need for the developer to implement a system from scratch that makes use of
* the native methods and components.
*
* TLDR:
* - This actor populates a captured scene (created in Capture Flow) by spawning child actors with predefined actor and mesh components.
* - Can be used as is, or can be derived or modified as needed depending on the application's needs.
*/
UCLASS(ClassGroup = OculusXRScene)
class OCULUSXRSCENE_API AOculusXRSceneActor : public AActor
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "OculusXR|Scene Actor")
void LaunchCaptureFlow();
UFUNCTION(BlueprintCallable, Category = "OculusXR|Scene Actor")
bool IsScenePopulated();
UFUNCTION(BlueprintCallable, Category = "OculusXR|Scene Actor", Meta = (DeprecatedFunction, DeprecationMessage = "Is Room Layout Valid is deprecated and no longer returns any value but true. Please validate your room configuration in the way your application requires."))
bool IsRoomLayoutValid();
UFUNCTION(BlueprintCallable, Category = "OculusXR|Scene Actor")
void PopulateScene();
UFUNCTION(BlueprintCallable, Category = "OculusXR|Scene Actor")
void ClearScene();
UFUNCTION(BlueprintCallable, Category = "OculusXR|Scene Actor")
void SetVisibilityToAllSceneAnchors(const bool bIsVisible);
UFUNCTION(BlueprintCallable, Category = "OculusXR|Scene Actor")
void SetVisibilityToSceneAnchorsBySemanticLabel(const FString SemanticLabel, const bool bIsVisible);
UFUNCTION(BlueprintCallable, Category = "OculusXR|Scene Actor")
TArray<AActor*> GetActorsBySemanticLabel(const FString SemanticLabel);
UFUNCTION(BlueprintPure, BlueprintCallable, Category = "OculusXR|Scene Actor")
TArray<FOculusXRRoomLayout> GetRoomLayouts() const;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "OculusXR|Scene Actor")
TEnumAsByte<EOculusXRLaunchCaptureFlowWhenMissingScene> LauchCaptureFlowWhenMissingScene = EOculusXRLaunchCaptureFlowWhenMissingScene::ALWAYS;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "OculusXR|Scene Actor", meta = (UIMin = 1, ClampMin = 1, UIMax = 1024, ClampMax = 1024))
int32 MaxQueries = 64;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "OculusXR|Scene Actor")
bool bPopulateSceneOnBeginPlay = true;
// If true then when the scene model is loaded we will only attempt to populate the room the user is standing in.
// Otherwise all rooms and all scene anchors will be loaded.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "OculusXR|Scene Actor")
bool bActiveRoomOnly = true;
UPROPERTY(EditAnywhere, Category = "OculusXR|Scene Actor")
TMap<FString, FOculusXRSpawnedSceneAnchorProperties> ScenePlaneSpawnedSceneAnchorProperties;
UPROPERTY(EditAnywhere, Category = "OculusXR|Scene Actor")
TMap<FString, FOculusXRSpawnedSceneAnchorProperties> SceneVolumeSpawnedSceneAnchorProperties;
public:
AOculusXRSceneActor(const FObjectInitializer& ObjectInitializer);
virtual void BeginPlay() override;
virtual void EndPlay(EEndPlayReason::Type Reason) override;
virtual void Tick(float DeltaTime) override;
virtual void PostLoad() override;
private:
EOculusXRAnchorResult::Type QueryAllRooms();
void RoomLayoutQueryComplete(EOculusXRAnchorResult::Type AnchorResult, const TArray<FOculusXRSpaceQueryResult>& QueryResults);
EOculusXRAnchorResult::Type QueryRoomUUIDs(const FOculusXRUInt64 RoomSpaceID, const TArray<FOculusXRUUID>& RoomUUIDs);
void SceneRoomQueryComplete(EOculusXRAnchorResult::Type AnchorResult, const TArray<FOculusXRSpaceQueryResult>& QueryResults, const FOculusXRUInt64 RoomSpaceID);
void StartSingleRoomQuery(FOculusXRUInt64 RoomSpaceID, FOculusXRRoomLayout RoomLayout);
EOculusXRAnchorResult::Type QueryFloorForActiveRoom(FOculusXRUInt64 RoomSpaceID, FOculusXRRoomLayout RoomLayout);
void ActiveRoomFloorQueryComplete(EOculusXRAnchorResult::Type AnchorResult, const TArray<FOculusXRSpaceQueryResult>& QueryResults, FOculusXRUInt64 RoomSpaceID, FOculusXRRoomLayout RoomLayout);
bool PointInPolygon2D(FVector2f PointToTest, const TArray<FVector2f>& PolyVerts) const;
void GetSemanticClassifications(uint64 Space, TArray<FString>& OutSemanticLabels) const;
// Scene capture event handler
void SceneCaptureComplete_Handler(FOculusXRUInt64 RequestId, bool bResult);
// Launches Capture Flow if (based on LauchCaptureFlowWhenMissingScene member value)
void LaunchCaptureFlowIfNeeded();
// Resets states of the Actor
void ResetStates();
// Validates UUID
bool IsValidUuid(const FOculusXRUUID& Uuid);
// Helper method to spawn an actor for anchor
AActor* SpawnActorWithSceneComponent(const FOculusXRUInt64& Space, const FOculusXRUInt64& RoomSpaceID, const TArray<FString>& SemanticClassifications, UClass* sceneAnchorComponentInstanceClass);
// Spawns a scene anchor
AActor* SpawnOrUpdateSceneAnchor(AActor* Anchor, const FOculusXRUInt64& Space, const FOculusXRUInt64& RoomSpaceID, const FVector& BoundedPos, const FVector& BoundedSize, const TArray<FString>& SemanticClassifications, const EOculusXRSpaceComponentType AnchorComponentType);
// Components for room layout and spatial anchors functionalities
UOculusXRRoomLayoutManagerComponent* RoomLayoutManagerComponent = nullptr;
class UOculusXRSceneGlobalMeshComponent* SceneGlobalMeshComponent = nullptr;
// Whether Capture Flow was already launched once
bool bCaptureFlowWasLaunched;
// Whether last room layout was valid
bool bRoomLayoutIsValid;
// Whether we found a captured scene
bool bFoundCapturedScene;
UPROPERTY(Transient)
TMap<FOculusXRUInt64, FOculusXRRoomLayout> RoomLayouts;
};

View File

@@ -0,0 +1,24 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "CoreMinimal.h"
#include "OculusXRAnchorComponent.h"
#include "OculusXRSceneAnchorComponent.generated.h"
UCLASS(meta = (DisplayName = "OculusXR Scene Anchor Component", BlueprintSpawnableComponent))
class OCULUSXRSCENE_API UOculusXRSceneAnchorComponent : public UOculusXRAnchorComponent
{
GENERATED_BODY()
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
public:
UOculusXRSceneAnchorComponent(const FObjectInitializer& ObjectInitializer);
UPROPERTY(Transient, BlueprintReadOnly, Category = "OculusXR|Scene Anchor Component")
TArray<FString> SemanticClassifications;
UPROPERTY(Transient, BlueprintReadOnly, Category = "OculusXR|Scene Anchor Component")
FOculusXRUInt64 RoomSpaceID = 0;
};

View File

@@ -0,0 +1,13 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "CoreTypes.h"
#include "OculusXRSceneTypes.h"
#include "Delegates/Delegate.h"
class FOculusXRSceneEventDelegates
{
public:
};

View File

@@ -0,0 +1,17 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include <Subsystems/EngineSubsystem.h>
#include "OculusXRSceneTypes.h"
#include "OculusXRSceneEventDelegates.generated.h"
UCLASS()
class UOculusXRSceneEventDelegates : public UEngineSubsystem
{
GENERATED_BODY()
public:
};

View File

@@ -0,0 +1,16 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "OculusXRAnchorTypes.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "OculusXRSceneFunctionLibrary.generated.h"
UCLASS()
class OCULUSXRSCENE_API UOculusXRSceneFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
};

View File

@@ -0,0 +1,49 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "CoreMinimal.h"
#include "OculusXRSceneAnchorComponent.h"
#include "Engine/CollisionProfile.h"
#include "OculusXRRoomLayoutManagerComponent.h"
#include "OculusXRSceneGlobalMeshComponent.generated.h"
class UMaterialInterface;
UCLASS(meta = (DisplayName = "OculusXR Scene Global Mesh Component", BlueprintSpawnableComponent))
class OCULUSXRSCENE_API UOculusXRSceneGlobalMeshComponent : public UActorComponent
{
GENERATED_BODY()
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
public:
UOculusXRSceneGlobalMeshComponent(const FObjectInitializer& ObjectInitializer);
void CreateMeshComponent(const FOculusXRUInt64& Space, AActor* GlobalMeshAnchor, const UOculusXRRoomLayoutManagerComponent* RoomLayoutManagerComponent) const;
static const FString GlobalMeshSemanticLabel;
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "OculusXR")
bool Collision = false;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "OculusXR")
FCollisionProfileName CollisionProfileName;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "OculusXR")
bool Visible = false;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "OculusXR")
UMaterialInterface* Material = nullptr;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "OculusXR")
TSoftClassPtr<UOculusXRSceneAnchorComponent> SceneAnchorComponent = TSoftClassPtr<UOculusXRSceneAnchorComponent>(FSoftClassPath(UOculusXRSceneAnchorComponent::StaticClass()));
public:
bool HasCollision() const;
bool IsVisible() const;
UClass* GetAnchorComponentClass() const;
};

View File

@@ -0,0 +1,31 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "OculusXRSceneTypes.h"
#include <Subsystems/GameInstanceSubsystem.h>
#include <Tickable.h>
#include "OculusXRSceneSubsystem.generated.h"
UCLASS()
class UOculusXRSceneSubsystem : public UGameInstanceSubsystem, public FTickableGameObject
{
GENERATED_BODY()
public:
UOculusXRSceneSubsystem();
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
// FTickableGameObject implementation Begin
virtual ETickableTickType GetTickableTickType() const override;
virtual bool IsAllowedToTick() const override final;
virtual void Tick(float DeltaTime) override;
virtual TStatId GetStatId() const override { RETURN_QUICK_DECLARE_CYCLE_STAT(UOculusXRSceneSubsystem, STATGROUP_Tickables); }
// FTickableGameObject implementation End
private:
};

View File

@@ -0,0 +1,14 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "OculusXRSceneTypes.generated.h"
USTRUCT()
struct FSceneTypesPlaceholder
{
GENERATED_BODY()
public:
};