// @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(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(this, USceneComponent::StaticClass()); rootSceneComponent->SetMobility(EComponentMobility::Static); rootSceneComponent->RegisterComponent(); SetRootComponent(rootSceneComponent); SceneGlobalMeshComponent = FindComponentByClass(); // 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& SemanticClassifications, UClass* sceneAnchorComponentInstanceClass) { FActorSpawnParameters actorSpawnParams; actorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; AActor* Anchor = GetWorld()->SpawnActor(AActor::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator, actorSpawnParams); USceneComponent* rootComponent = NewObject(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(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& 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* sceneAnchorComponentClassPtrRef = &foundProperties->ActorComponent; TSoftObjectPtr* 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(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 childrenComponents = RootComponent->GetAttachChildren(); for (USceneComponent* SceneComponent : childrenComponents) { Cast(SceneComponent->GetOuter())->Destroy(); } bRoomLayoutIsValid = false; bFoundCapturedScene = false; } void AOculusXRSceneActor::SetVisibilityToAllSceneAnchors(const bool bIsVisible) { if (!RootComponent) return; TArray 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 childrenComponents = RootComponent->GetAttachChildren(); for (USceneComponent* sceneComponent : childrenComponents) { UObject* outerObject = sceneComponent->GetOuter(); if (!outerObject) { continue; } AActor* outerActor = Cast(outerObject); if (!outerActor) { continue; } UActorComponent* sceneAnchorComponent = outerActor->GetComponentByClass(UOculusXRSceneAnchorComponent::StaticClass()); if (!sceneAnchorComponent) { continue; } if (Cast(sceneAnchorComponent)->SemanticClassifications.Contains(label)) { sceneComponent->SetVisibility(bIsVisible, true); } } } TArray 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 actors; if (!RootComponent) return actors; TArray childrenComponents = RootComponent->GetAttachChildren(); for (USceneComponent* sceneComponent : childrenComponents) { UObject* outerObject = sceneComponent->GetOuter(); if (!outerObject) { continue; } AActor* outerActor = Cast(outerObject); if (!outerActor) { continue; } UActorComponent* sceneAnchorComponent = outerActor->GetComponentByClass(UOculusXRSceneAnchorComponent::StaticClass()); if (!sceneAnchorComponent) { continue; } if (Cast(sceneAnchorComponent)->SemanticClassifications.Contains(label)) { actors.Add(outerActor); } } return actors; } TArray AOculusXRSceneActor::GetRoomLayouts() const { TArray 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& 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& 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& QueryResults, const FOculusXRUInt64 RoomSpaceID) { if (!UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(AnchorResult)) { return; } bool bOutPending = false; for (auto& AnchorQueryElement : QueryResults) { if (SceneGlobalMeshComponent) { TArray 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 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 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({ RoomLayout.FloorUuid }), EOculusXRSpaceStorageLocation::Local, FOculusXRAnchorQueryDelegate::CreateUObject(this, &AOculusXRSceneActor::ActiveRoomFloorQueryComplete, RoomSpaceID, RoomLayout), anchorQueryResult); return anchorQueryResult; } void AOculusXRSceneActor::ActiveRoomFloorQueryComplete(EOculusXRAnchorResult::Type AnchorResult, const TArray& 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 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 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 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 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& 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& 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