VRTowerDef/Plugins/MetaXR/Source/OculusXRScene/Private/OculusXRSceneActor.cpp

758 lines
27 KiB
C++

// @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