356 lines
12 KiB
C++
356 lines
12 KiB
C++
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
|
|
#include "MRUtilityKitData.h"
|
|
#include "MRUtilityKitSubsystem.h"
|
|
#include "MRUtilityKitSerializationHelpers.h"
|
|
#include "MRUtilityKitTelemetry.h"
|
|
#include "OculusXRAnchorBPFunctionLibrary.h"
|
|
#include "OculusXRScene.h"
|
|
#include "Engine/World.h"
|
|
#include "Engine/GameInstance.h"
|
|
#include "GameFramework/WorldSettings.h"
|
|
#include "Serialization/JsonReader.h"
|
|
#include "Serialization/JsonSerializer.h"
|
|
|
|
AMRUKLocalizer::AMRUKLocalizer()
|
|
{
|
|
PrimaryActorTick.bCanEverTick = true;
|
|
PrimaryActorTick.bStartWithTickEnabled = true;
|
|
}
|
|
|
|
void AMRUKLocalizer::Tick(float DeltaTime)
|
|
{
|
|
for (int i = 0; i < AnchorsData.Num(); ++i)
|
|
{
|
|
const auto Query = AnchorsData[i];
|
|
if (UOculusXRAnchorBPFunctionLibrary::GetAnchorTransformByHandle(Query->SpaceQuery.Space, Query->Transform))
|
|
{
|
|
Query->NeedAnchorLocalization = false;
|
|
if (Query->SemanticClassifications.IsEmpty())
|
|
{
|
|
UE_LOG(LogMRUK, Log, TEXT("Localized anchor %s"), *Query->SpaceQuery.UUID.ToString());
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogMRUK, Log, TEXT("Localized anchor %s - %s"), *Query->SpaceQuery.UUID.ToString(), *Query->SemanticClassifications[0]);
|
|
}
|
|
AnchorsData.RemoveAt(i);
|
|
--i;
|
|
}
|
|
}
|
|
|
|
if (AnchorsData.IsEmpty())
|
|
{
|
|
UE_LOG(LogMRUK, Log, TEXT("All anchors localized"));
|
|
OnComplete.Broadcast(true);
|
|
}
|
|
}
|
|
|
|
void UMRUKAnchorData::LoadFromDevice(const FOculusXRAnchorsDiscoverResult& AnchorsDiscoverResult)
|
|
{
|
|
SpaceQuery = AnchorsDiscoverResult;
|
|
|
|
Transform = FTransform::Identity;
|
|
NeedAnchorLocalization = false;
|
|
if (!UOculusXRAnchorBPFunctionLibrary::GetAnchorTransformByHandle(SpaceQuery.Space, Transform))
|
|
{
|
|
UE_LOG(LogMRUK, Log, TEXT("Anchor %s is not localized yet. Localize it async."), *SpaceQuery.UUID.ToString());
|
|
NeedAnchorLocalization = true;
|
|
}
|
|
|
|
EOculusXRAnchorResult::Type Result = OculusXRScene::FOculusXRScene::GetSemanticClassification(SpaceQuery.Space.Value, SemanticClassifications);
|
|
if (!UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(Result))
|
|
{
|
|
UE_LOG(LogMRUK, Error, TEXT("Failed to get semantic classification space for %s."), *SpaceQuery.UUID.ToString());
|
|
}
|
|
|
|
const UWorld* World = GetWorld();
|
|
const float WorldToMeters = World ? World->GetWorldSettings()->WorldToMeters : 100.0;
|
|
|
|
FVector ScenePlanePos;
|
|
FVector ScenePlaneSize;
|
|
Result = OculusXRScene::FOculusXRScene::GetScenePlane(SpaceQuery.Space, ScenePlanePos, ScenePlaneSize);
|
|
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(Result))
|
|
{
|
|
const FVector2D PlanePos = FVector2D(ScenePlanePos.Y, ScenePlanePos.Z) * WorldToMeters;
|
|
const FVector2D PlaneSize = FVector2D(ScenePlaneSize.Y, ScenePlaneSize.Z) * WorldToMeters;
|
|
PlaneBounds = FBox2D(PlanePos, PlanePos + PlaneSize);
|
|
TArray<FVector2f> SpaceBoundary2D;
|
|
Result = OculusXRScene::FOculusXRScene::GetBoundary2D(SpaceQuery.Space, SpaceBoundary2D);
|
|
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(Result))
|
|
{
|
|
PlaneBoundary2D.Reserve(SpaceBoundary2D.Num());
|
|
for (int i = 0; i < SpaceBoundary2D.Num(); ++i)
|
|
{
|
|
PlaneBoundary2D.Push(FVector2D(SpaceBoundary2D[i].X * WorldToMeters, SpaceBoundary2D[i].Y * WorldToMeters));
|
|
}
|
|
}
|
|
}
|
|
|
|
FVector SceneVolumePos;
|
|
FVector SceneVolumeSize;
|
|
Result = OculusXRScene::FOculusXRScene::GetSceneVolume(SpaceQuery.Space, SceneVolumePos, SceneVolumeSize);
|
|
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(Result))
|
|
{
|
|
const FVector VolumePos = SceneVolumePos * WorldToMeters;
|
|
const FVector VolumeSize = SceneVolumeSize * WorldToMeters;
|
|
VolumeBounds = FBox(VolumePos, VolumePos + VolumeSize);
|
|
}
|
|
}
|
|
|
|
void UMRUKAnchorData::LoadFromJson(const FJsonValue& Value)
|
|
{
|
|
const auto Object = Value.AsObject();
|
|
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("UUID")), SpaceQuery.UUID);
|
|
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("SemanticClassifications")), SemanticClassifications);
|
|
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("Transform")), Transform);
|
|
if (const auto JsonValue = Object->TryGetField(TEXT("PlaneBounds")))
|
|
{
|
|
MRUKDeserialize(*JsonValue, PlaneBounds);
|
|
}
|
|
if (const auto JsonValue = Object->TryGetField(TEXT("PlaneBoundary2D")))
|
|
{
|
|
MRUKDeserialize(*JsonValue, PlaneBoundary2D);
|
|
}
|
|
if (const auto JsonValue = Object->TryGetField(TEXT("VolumeBounds")))
|
|
{
|
|
MRUKDeserialize(*JsonValue, VolumeBounds);
|
|
}
|
|
NeedAnchorLocalization = false;
|
|
}
|
|
|
|
void UMRUKRoomData::LoadFromDevice(UMRUKSceneData* Data, const FOculusXRAnchorsDiscoverResult& AnchorsDiscoverResult)
|
|
{
|
|
SceneData = Data;
|
|
|
|
SpaceQuery = AnchorsDiscoverResult;
|
|
|
|
const auto Subsystem = GetWorld()->GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
|
|
|
if (!Subsystem->GetRoomLayoutManager()->GetRoomLayout(SpaceQuery.Space.Value, RoomLayout))
|
|
{
|
|
UE_LOG(LogMRUK, Error, TEXT("Could not query room layout"));
|
|
FinishQuery(false);
|
|
return;
|
|
}
|
|
|
|
EOculusXRAnchorResult::Type Result{};
|
|
|
|
const auto Filter = NewObject<UOculusXRSpaceDiscoveryIdsFilter>(this);
|
|
Filter->Uuids = RoomLayout.RoomObjectUUIDs;
|
|
FOculusXRSpaceDiscoveryInfo DiscoveryInfo{};
|
|
DiscoveryInfo.Filters.Push(Filter);
|
|
|
|
OculusXRAnchors::FOculusXRAnchors::DiscoverAnchors(DiscoveryInfo, FOculusXRDiscoverAnchorsResultsDelegate::CreateUObject(this, &UMRUKRoomData::RoomDataLoadedIncrementalResults), FOculusXRDiscoverAnchorsCompleteDelegate::CreateUObject(this, &UMRUKRoomData::RoomDataLoadedComplete), Result);
|
|
if (Result != EOculusXRAnchorResult::Success)
|
|
{
|
|
UE_LOG(LogMRUK, Error, TEXT("Failed to discover anchors"));
|
|
FinishQuery(false);
|
|
}
|
|
}
|
|
|
|
void UMRUKRoomData::LoadFromJson(UMRUKSceneData* Data, const FJsonValue& Value)
|
|
{
|
|
SceneData = Data;
|
|
|
|
const auto Object = Value.AsObject();
|
|
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("UUID")), SpaceQuery.UUID);
|
|
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("RoomLayout")), RoomLayout);
|
|
auto AnchorsJson = Object->GetArrayField(TEXT("Anchors"));
|
|
for (const auto& AnchorJson : AnchorsJson)
|
|
{
|
|
auto AnchorQuery = NewObject<UMRUKAnchorData>(this);
|
|
AnchorsData.Push(AnchorQuery);
|
|
RoomLayout.RoomObjectUUIDs.Add(AnchorQuery->SpaceQuery.UUID);
|
|
AnchorQuery->LoadFromJson(*AnchorJson);
|
|
}
|
|
FinishQuery(true);
|
|
}
|
|
|
|
void UMRUKRoomData::FinishQuery(bool Success)
|
|
{
|
|
OnComplete.Broadcast(Success);
|
|
}
|
|
|
|
void UMRUKRoomData::RoomDataLoadedComplete(EOculusXRAnchorResult::Type Result)
|
|
{
|
|
if (!UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(Result))
|
|
{
|
|
UE_LOG(LogMRUK, Error, TEXT("Discovering room data failed"));
|
|
FinishQuery(false);
|
|
return;
|
|
}
|
|
|
|
if (AnchorsData.Num() == 0)
|
|
{
|
|
UE_LOG(LogMRUK, Warning, TEXT("Discovered room which doesn't contain any anchors. Skip that room"));
|
|
SceneData->RoomsData.Remove(this);
|
|
AnchorsInitialized(true);
|
|
return;
|
|
}
|
|
|
|
TArray<UMRUKAnchorData*> AnchorQueriesLocalization;
|
|
|
|
for (auto& AnchorQuery : AnchorsData)
|
|
{
|
|
if (AnchorQuery->NeedAnchorLocalization)
|
|
{
|
|
AnchorQueriesLocalization.Push(AnchorQuery);
|
|
}
|
|
}
|
|
|
|
if (!AnchorQueriesLocalization.IsEmpty())
|
|
{
|
|
UE_LOG(LogMRUK, Log, TEXT("Could not localize all anchors. Going to localize them async"));
|
|
FActorSpawnParameters ActorSpawnParams;
|
|
ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
LocalizationActor = GetWorld()->SpawnActor<AMRUKLocalizer>(ActorSpawnParams);
|
|
LocalizationActor->AnchorsData = AnchorQueriesLocalization;
|
|
LocalizationActor->OnComplete.AddDynamic(this, &UMRUKRoomData::AnchorsInitialized);
|
|
}
|
|
else
|
|
{
|
|
AnchorsInitialized(true);
|
|
}
|
|
}
|
|
|
|
void UMRUKRoomData::RoomDataLoadedIncrementalResults(const TArray<FOculusXRAnchorsDiscoverResult>& DiscoverResults)
|
|
{
|
|
// NOTE: This function may be called multiple times in batches. E.g. if there are 18 anchors in a room, this may
|
|
// be called once with 10 anchors and a second time with 8 anchors in DiscoverResults.
|
|
UE_LOG(LogMRUK, Log, TEXT("Received %d anchors from device"), DiscoverResults.Num());
|
|
|
|
for (auto& DiscoverResult : DiscoverResults)
|
|
{
|
|
auto AnchorQuery = NewObject<UMRUKAnchorData>(this);
|
|
AnchorQuery->LoadFromDevice(DiscoverResult);
|
|
AnchorsData.Push(AnchorQuery);
|
|
}
|
|
}
|
|
|
|
void UMRUKRoomData::AnchorsInitialized(bool Success)
|
|
{
|
|
UE_LOG(LogMRUK, Log, TEXT("Anchors data initialized Success==%d"), Success);
|
|
if (IsValid(LocalizationActor))
|
|
{
|
|
LocalizationActor->Destroy();
|
|
LocalizationActor = nullptr;
|
|
}
|
|
FinishQuery(Success);
|
|
}
|
|
|
|
void UMRUKSceneData::LoadFromDevice()
|
|
{
|
|
NumRoomsLeftToInitialize = 0;
|
|
|
|
EOculusXRAnchorResult::Type Result{};
|
|
|
|
const auto Filter = NewObject<UOculusXRSpaceDiscoveryComponentsFilter>(this);
|
|
Filter->ComponentType = EOculusXRSpaceComponentType::RoomLayout;
|
|
FOculusXRSpaceDiscoveryInfo DiscoveryInfo{};
|
|
DiscoveryInfo.Filters.Push(Filter);
|
|
|
|
OculusXRAnchors::FOculusXRAnchors::DiscoverAnchors(DiscoveryInfo, FOculusXRDiscoverAnchorsResultsDelegate::CreateUObject(this, &UMRUKSceneData::SceneDataLoadedComplete), FOculusXRDiscoverAnchorsCompleteDelegate::CreateUObject(this, &UMRUKSceneData::SceneDataLoadedResult), Result);
|
|
if (Result != EOculusXRAnchorResult::Success)
|
|
{
|
|
UE_LOG(LogMRUK, Error, TEXT("Failed to discover room layouts"));
|
|
FinishQuery(false);
|
|
}
|
|
}
|
|
|
|
|
|
void UMRUKSceneData::LoadFromJson(const FString& Json)
|
|
{
|
|
TSharedPtr<FJsonValue> Value;
|
|
const TSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(Json);
|
|
if (!FJsonSerializer::Deserialize(JsonReader, Value))
|
|
{
|
|
UE_LOG(LogMRUK, Warning, TEXT("Could not deserialize JSON scene data: %s"), *JsonReader->GetErrorMessage());
|
|
FinishQuery(false);
|
|
return;
|
|
}
|
|
|
|
const auto Object = Value->AsObject();
|
|
auto RoomsJson = Object->GetArrayField(TEXT("Rooms"));
|
|
|
|
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadSceneFromJsonMarker> Event(static_cast<int>(GetTypeHash(this)));
|
|
Event.AddAnnotation("NumRooms", TCHAR_TO_ANSI(*FString::FromInt(RoomsJson.Num())));
|
|
Event.SetResult(RoomsJson.Num() > 0 ? OculusXRTelemetry::EAction::Success : OculusXRTelemetry::EAction::Fail);
|
|
|
|
if (RoomsJson.IsEmpty())
|
|
{
|
|
UE_LOG(LogMRUK, Warning, TEXT("Could not find Rooms in JSON"));
|
|
FinishQuery(false);
|
|
return;
|
|
}
|
|
NumRoomsLeftToInitialize = RoomsJson.Num();
|
|
UE_LOG(LogMRUK, Log, TEXT("Found %d rooms in JSON"), NumRoomsLeftToInitialize);
|
|
for (const auto& RoomJson : RoomsJson)
|
|
{
|
|
auto RoomQuery = NewObject<UMRUKRoomData>(this);
|
|
RoomsData.Push(RoomQuery);
|
|
RoomQuery->OnComplete.AddDynamic(this, &UMRUKSceneData::RoomQueryComplete);
|
|
RoomQuery->LoadFromJson(this, *RoomJson);
|
|
}
|
|
}
|
|
|
|
void UMRUKSceneData::FinishQuery(bool Success)
|
|
{
|
|
if (!Success)
|
|
{
|
|
AnyRoomFailed = true;
|
|
}
|
|
--NumRoomsLeftToInitialize;
|
|
if (NumRoomsLeftToInitialize <= 0)
|
|
{
|
|
OnComplete.Broadcast(!AnyRoomFailed);
|
|
}
|
|
}
|
|
|
|
void UMRUKSceneData::SceneDataLoadedResult(EOculusXRAnchorResult::Type Result)
|
|
{
|
|
if (!UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(Result) || RoomsData.IsEmpty())
|
|
{
|
|
UE_LOG(LogMRUK, Error, TEXT("Discovering room layouts failed"));
|
|
FinishQuery(false);
|
|
}
|
|
}
|
|
|
|
void UMRUKSceneData::SceneDataLoadedComplete(const TArray<FOculusXRAnchorsDiscoverResult>& DiscoverResults)
|
|
{
|
|
NumRoomsLeftToInitialize = DiscoverResults.Num();
|
|
UE_LOG(LogMRUK, Log, TEXT("Found on %d rooms on the device"), NumRoomsLeftToInitialize);
|
|
|
|
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadSceneFromDeviceMarker> Event(static_cast<int>(GetTypeHash(this)));
|
|
Event.AddAnnotation("NumRooms", TCHAR_TO_ANSI(*FString::FromInt(DiscoverResults.Num())));
|
|
Event.SetResult(DiscoverResults.Num() > 0 ? OculusXRTelemetry::EAction::Success : OculusXRTelemetry::EAction::Fail);
|
|
|
|
if (NumRoomsLeftToInitialize == 0)
|
|
{
|
|
UE_LOG(LogMRUK, Error, TEXT("No room layouts discovered"));
|
|
FinishQuery(false);
|
|
return;
|
|
}
|
|
|
|
for (auto& DiscoverResult : DiscoverResults)
|
|
{
|
|
auto RoomQuery = NewObject<UMRUKRoomData>(this);
|
|
RoomsData.Push(RoomQuery);
|
|
RoomQuery->OnComplete.AddDynamic(this, &UMRUKSceneData::RoomQueryComplete);
|
|
RoomQuery->LoadFromDevice(this, DiscoverResult);
|
|
}
|
|
}
|
|
|
|
void UMRUKSceneData::RoomQueryComplete(bool Success)
|
|
{
|
|
if (!Success)
|
|
{
|
|
AnyRoomFailed = true;
|
|
}
|
|
--NumRoomsLeftToInitialize;
|
|
if (NumRoomsLeftToInitialize == 0)
|
|
{
|
|
FinishQuery(!AnyRoomFailed);
|
|
}
|
|
}
|