Android build settings + metaxr
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class MRUtilityKit : ModuleRules
|
||||
{
|
||||
public MRUtilityKit(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
bUseUnity = true;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
"RenderCore",
|
||||
"Projects"
|
||||
});
|
||||
|
||||
|
||||
if (Target.Version.MajorVersion > 5 || (Target.Version.MajorVersion == 5 && Target.Version.MinorVersion >= 3))
|
||||
{
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"XRBase",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"OculusXRHMD",
|
||||
"OculusXRAnchors",
|
||||
"OculusXRScene",
|
||||
"Json",
|
||||
"ProceduralMeshComponent",
|
||||
"HeadMountedDisplay",
|
||||
"MRUtilityKitShared",
|
||||
});
|
||||
|
||||
if (Target.bBuildEditor == true)
|
||||
{
|
||||
PrivateDependencyModuleNames.Add("UnrealEd");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
// @generated by `buck2 run //arvr/projects/mixedreality/libraries/mrutilitykit:build_and_deploy unreal`
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <float.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "CoreTypes.h"
|
||||
#include "Math/UnrealMath.h"
|
||||
#include "Math/MathFwd.h"
|
||||
|
||||
struct MRUKShared
|
||||
{
|
||||
static MRUKShared* GetInstance() { return Instance; }
|
||||
|
||||
static void LoadMRUKSharedLibrary();
|
||||
static void FreeMRUKSharedLibrary();
|
||||
|
||||
struct MrukSceneAnchor;
|
||||
|
||||
struct MrukRoomAnchor;
|
||||
|
||||
struct MrukUuid;
|
||||
|
||||
enum MrukSceneModel
|
||||
{
|
||||
MRUK_SCENE_MODEL_V2_FALLBACK_V1,
|
||||
MRUK_SCENE_MODEL_V1,
|
||||
MRUK_SCENE_MODEL_V2,
|
||||
};
|
||||
|
||||
enum MrukLogLevel
|
||||
{
|
||||
MRUK_LOG_LEVEL_DEBUG,
|
||||
MRUK_LOG_LEVEL_INFO,
|
||||
MRUK_LOG_LEVEL_WARN,
|
||||
MRUK_LOG_LEVEL_ERROR,
|
||||
};
|
||||
|
||||
enum MrukResult
|
||||
{
|
||||
MRUK_SUCCESS,
|
||||
MRUK_ERROR_INVALID_ARGS,
|
||||
MRUK_ERROR_UNKNOWN,
|
||||
MRUK_ERROR_INTERNAL,
|
||||
MRUK_ERROR_DISCOVERY_ONGOING,
|
||||
MRUK_ERROR_INVALID_JSON,
|
||||
MRUK_ERROR_NO_ROOMS_FOUND,
|
||||
MRUK_ERROR_INSUFFICIENT_RESOURCES,
|
||||
MRUK_ERROR_STORAGE_AT_CAPACITY,
|
||||
MRUK_ERROR_INSUFFICIENT_VIEW,
|
||||
MRUK_ERROR_PERMISSION_INSUFFICIENT,
|
||||
MRUK_ERROR_RATE_LIMITED,
|
||||
MRUK_ERROR_TOO_DARK,
|
||||
MRUK_ERROR_TOO_BRIGHT,
|
||||
};
|
||||
|
||||
enum MrukSurfaceType
|
||||
{
|
||||
MRUK_SURFACE_TYPE_NONE,
|
||||
MRUK_SURFACE_TYPE_PLANE,
|
||||
MRUK_SURFACE_TYPE_VOLUME,
|
||||
MRUK_SURFACE_TYPE_MESH,
|
||||
MRUK_SURFACE_TYPE_ALL,
|
||||
};
|
||||
|
||||
typedef void (*LogPrinter)(MrukLogLevel logLevel, const char* message);
|
||||
|
||||
typedef void (*MrukOnPreRoomAnchorAdded)(const MrukRoomAnchor* roomAnchor, void* userContext);
|
||||
|
||||
typedef void (*MrukOnRoomAnchorAdded)(const MrukRoomAnchor* roomAnchor, void* userContext);
|
||||
|
||||
typedef void (*MrukOnRoomAnchorUpdated)(const MrukRoomAnchor* roomAnchor, const MrukUuid* oldRoomAnchorUuid, void* userContext);
|
||||
|
||||
typedef void (*MrukOnRoomAnchorRemoved)(const MrukRoomAnchor* roomAnchor, void* userContext);
|
||||
|
||||
typedef void (*MrukOnSceneAnchorAdded)(const MrukSceneAnchor* sceneAnchor, void* userContext);
|
||||
|
||||
typedef void (*MrukOnSceneAnchorUpdated)(const MrukSceneAnchor* sceneAnchor, void* userContext);
|
||||
|
||||
typedef void (*MrukOnSceneAnchorRemoved)(const MrukSceneAnchor* sceneAnchor, void* userContext);
|
||||
|
||||
typedef void (*MrukOnDiscoveryFinished)(MrukResult result, void* userContext);
|
||||
|
||||
struct MrukQuatf
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float w;
|
||||
};
|
||||
|
||||
struct MrukPosef
|
||||
{
|
||||
FVector3f position;
|
||||
MrukQuatf rotation;
|
||||
};
|
||||
|
||||
struct MrukPolygon2f
|
||||
{
|
||||
const FVector2f* points;
|
||||
uint32_t numPoints;
|
||||
};
|
||||
|
||||
struct MrukMesh2f
|
||||
{
|
||||
FVector2f* vertices;
|
||||
uint32_t numVertices;
|
||||
uint32_t* indices;
|
||||
uint32_t numIndices;
|
||||
};
|
||||
|
||||
struct MrukMesh3f
|
||||
{
|
||||
FVector3f* vertices;
|
||||
uint32_t numVertices;
|
||||
uint32_t* indices;
|
||||
uint32_t numIndices;
|
||||
};
|
||||
|
||||
struct MrukUuid
|
||||
{
|
||||
uint64_t part1;
|
||||
uint64_t part2;
|
||||
};
|
||||
|
||||
struct MrukVolume
|
||||
{
|
||||
FVector3f min;
|
||||
FVector3f max;
|
||||
};
|
||||
|
||||
struct MrukPlane
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
float width;
|
||||
float height;
|
||||
};
|
||||
|
||||
struct MrukSceneAnchor
|
||||
{
|
||||
uint64_t space;
|
||||
MrukUuid uuid;
|
||||
MrukUuid roomUuid;
|
||||
MrukPosef pose;
|
||||
MrukVolume volume;
|
||||
MrukPlane plane;
|
||||
char** semanticLabels;
|
||||
FVector2f* planeBoundary;
|
||||
uint32_t* globalMeshIndices;
|
||||
FVector3f* globalMeshPositions;
|
||||
uint32_t semanticLabelsCount;
|
||||
uint32_t planeBoundaryCount;
|
||||
uint32_t globalMeshIndicesCount;
|
||||
uint32_t globalMeshPositionsCount;
|
||||
bool hasVolume;
|
||||
bool hasPlane;
|
||||
};
|
||||
|
||||
struct MrukRoomAnchor
|
||||
{
|
||||
uint64_t space;
|
||||
MrukUuid uuid;
|
||||
};
|
||||
|
||||
struct MrukEventListener
|
||||
{
|
||||
MrukOnPreRoomAnchorAdded onPreRoomAnchorAdded;
|
||||
MrukOnRoomAnchorAdded onRoomAnchorAdded;
|
||||
MrukOnRoomAnchorUpdated onRoomAnchorUpdated;
|
||||
MrukOnRoomAnchorRemoved onRoomAnchorRemoved;
|
||||
MrukOnSceneAnchorAdded onSceneAnchorAdded;
|
||||
MrukOnSceneAnchorUpdated onSceneAnchorUpdated;
|
||||
MrukOnSceneAnchorRemoved onSceneAnchorRemoved;
|
||||
MrukOnDiscoveryFinished onDiscoveryFinished;
|
||||
void* userContext;
|
||||
};
|
||||
|
||||
struct MrukHit
|
||||
{
|
||||
MrukUuid roomAnchorUuid;
|
||||
MrukUuid sceneAnchorUuid;
|
||||
float hitDistance;
|
||||
FVector3f hitPosition;
|
||||
FVector3f hitNormal;
|
||||
};
|
||||
|
||||
void (*SetLogPrinter)(LogPrinter printer);
|
||||
|
||||
/**
|
||||
* Create the global anchor store with a external OpenXR instance and session.
|
||||
* This should only be called once on application startup.
|
||||
* Make sure to hook up the ContextOnOpenXrEvent() function as well.
|
||||
* If the context is not needed anymore it should be destroyed with ContextDestroy() to free
|
||||
* resources.
|
||||
*/
|
||||
MrukResult (*AnchorStoreCreate)(uint64_t xrInstance, uint64_t xrSession, void* xrInstanceProcAddrFunc, uint64_t baseSpace);
|
||||
MrukResult (*AnchorStoreCreateWithoutOpenXr)();
|
||||
|
||||
/**
|
||||
* Destroy the global anchor store
|
||||
* This should only be called once on application shutdown.
|
||||
*/
|
||||
void (*AnchorStoreDestroy)();
|
||||
|
||||
/**
|
||||
* If the base space changes after initialization, this function should be called to update the
|
||||
* base space.
|
||||
*/
|
||||
void (*AnchorStoreSetBaseSpace)(uint64_t baseSpace);
|
||||
|
||||
/**
|
||||
* Start anchor discovery in the anchor store
|
||||
*/
|
||||
MrukResult (*AnchorStoreStartDiscovery)(bool shouldRemoveMissingRooms, MrukSceneModel sceneModel);
|
||||
|
||||
/**
|
||||
* Load the scene from a json string
|
||||
*/
|
||||
MrukResult (*AnchorStoreLoadSceneFromJson)(const char* jsonString, bool shouldRemoveMissingRooms, MrukSceneModel sceneModel);
|
||||
|
||||
/**
|
||||
* Save the scene to a json string.
|
||||
* @return The serialized JSON string. This string must be freed with FreeAnchorStoreJson after use!
|
||||
*/
|
||||
const char* (*AnchorStoreSaveSceneToJson)();
|
||||
|
||||
/**
|
||||
* Free the json string returned by AnchorStoreSaveSceneToJson.
|
||||
* @param[in] jsonString The JSON string to free.
|
||||
*/
|
||||
void (*AnchorStoreFreeJson)(const char* jsonString);
|
||||
|
||||
/**
|
||||
* Clear and remove all rooms in the anchor store.
|
||||
*/
|
||||
void (*AnchorStoreClearRooms)();
|
||||
|
||||
/**
|
||||
* Clear and remove the room that matches the given uuid.
|
||||
*/
|
||||
void (*AnchorStoreClearRoom)(MrukUuid roomUuid);
|
||||
|
||||
/**
|
||||
* Allows to forward OpenXR events from the engine into the shared library
|
||||
*/
|
||||
void (*AnchorStoreOnOpenXrEvent)(void* baseEventHeader);
|
||||
|
||||
/**
|
||||
* Needs to be called every tick by the engine.
|
||||
*/
|
||||
void (*AnchorStoreTick)(uint64_t nextPredictedDisplayTime);
|
||||
void (*AnchorStoreRegisterEventListener)(MrukEventListener listener);
|
||||
|
||||
/**
|
||||
* Cast a ray against all anchors in the room and return the first hit.
|
||||
*/
|
||||
bool (*AnchorStoreRaycastRoom)(MrukUuid roomUuid, FVector3f origin, FVector3f direction, float maxDistance, uint32_t surfaceType, MrukHit* outHit);
|
||||
|
||||
/**
|
||||
* Cast a ray against all anchors in the room and return all hits along the ray.
|
||||
*/
|
||||
bool (*AnchorStoreRaycastRoomAll)(MrukUuid roomUuid, FVector3f origin, FVector3f direction, float maxDistance, uint32_t surfaceType, MrukHit* outHits, uint32_t* outHitsCount);
|
||||
bool (*AnchorStoreIsDiscoveryRunning)();
|
||||
|
||||
/**
|
||||
* Add two vectors together. This is implemented as a test to ensure the native shared
|
||||
* library is working correctly.
|
||||
*
|
||||
* @param[in] a The first vector.
|
||||
* @param[in] b The second vector.
|
||||
* @return The sum of the two vectors.
|
||||
*/
|
||||
FVector3f (*AddVectors)(FVector3f a, FVector3f b);
|
||||
|
||||
/**
|
||||
* Triangulate a polygon with holes, any winding order works. The first polyline defines the main
|
||||
* polygon. Following polylines define holes. This function will allocate memory for the vertices
|
||||
* and indices. You *MUST* call FreeMesh() when you are done with it or you will leak memory.
|
||||
*
|
||||
* @param[in] polygons The polygon to triangulate.
|
||||
* @param[in] numPolygons The number of polygons in the array.
|
||||
* @return mesh The triangulated mesh.
|
||||
*/
|
||||
MrukMesh2f (*TriangulatePolygon)(const MrukPolygon2f* polygons, uint32_t numPolygons);
|
||||
|
||||
/**
|
||||
* Free the memory allocated by TriangulatePolygon.
|
||||
*
|
||||
* @param[in] mesh The mesh to free.
|
||||
*/
|
||||
void (*FreeMesh)(MrukMesh2f* mesh);
|
||||
|
||||
/**
|
||||
* Compute the mesh segmentation for a given set of vertices, indices and segmentation points.
|
||||
* You *MUST* call FreeMeshSegmentation() on the meshSegments array when you are done with it or you
|
||||
* will leak memory.
|
||||
*
|
||||
* @param[in] vertices The mesh vertices.
|
||||
* @param[in] numVertices The number of vertices in the mesh.
|
||||
* @param[in] indices The mesh indices.
|
||||
* @param[in] numIndices The number of indices in the mesh.
|
||||
* @param[in] segmentationPoints The points that should be used to calculate the segments.
|
||||
* @param[in] numSegmentationPoints The number of segmentation points.
|
||||
* @param[in] reservedMin The minimum bounding box for the reserved segment.
|
||||
* @param[in] reservedMax The maximum bounding box for the reserved segment.
|
||||
* @param[out] meshSegments The resulting segments.
|
||||
* @param[out] numSegments The number of segments in the resulting array.
|
||||
* @param[out] reservedSegment The segment that is inside the reserved bounding box.
|
||||
*/
|
||||
MrukResult (*ComputeMeshSegmentation)(const FVector3f* vertices, uint32_t numVertices, const uint32_t* indices, uint32_t numIndices, const FVector3f* segmentationPoints, uint32_t numSegmentationPoints, FVector3f reservedMin, FVector3f reservedMax, MrukMesh3f** meshSegments, uint32_t* numSegments, MrukMesh3f* reservedSegment);
|
||||
|
||||
/**
|
||||
* Free the memory allocated by ComputeMeshSegmentation.
|
||||
*
|
||||
* @param[in] meshSegments The array of segments to free.
|
||||
* @param[in] numSegments The number of segments in the array.
|
||||
* @param[in] reservedSegment The reserved segment to free.
|
||||
*/
|
||||
void (*FreeMeshSegmentation)(const MrukMesh3f* meshSegments, uint32_t numSegments, MrukMesh3f* reservedSegment);
|
||||
|
||||
private:
|
||||
|
||||
void LoadNativeFunctions()
|
||||
{
|
||||
SetLogPrinter = reinterpret_cast<decltype(SetLogPrinter)>(LoadFunction(TEXT("SetLogPrinter")));
|
||||
AnchorStoreCreate = reinterpret_cast<decltype(AnchorStoreCreate)>(LoadFunction(TEXT("AnchorStoreCreate")));
|
||||
AnchorStoreCreateWithoutOpenXr = reinterpret_cast<decltype(AnchorStoreCreateWithoutOpenXr)>(LoadFunction(TEXT("AnchorStoreCreateWithoutOpenXr")));
|
||||
AnchorStoreDestroy = reinterpret_cast<decltype(AnchorStoreDestroy)>(LoadFunction(TEXT("AnchorStoreDestroy")));
|
||||
AnchorStoreSetBaseSpace = reinterpret_cast<decltype(AnchorStoreSetBaseSpace)>(LoadFunction(TEXT("AnchorStoreSetBaseSpace")));
|
||||
AnchorStoreStartDiscovery = reinterpret_cast<decltype(AnchorStoreStartDiscovery)>(LoadFunction(TEXT("AnchorStoreStartDiscovery")));
|
||||
AnchorStoreLoadSceneFromJson = reinterpret_cast<decltype(AnchorStoreLoadSceneFromJson)>(LoadFunction(TEXT("AnchorStoreLoadSceneFromJson")));
|
||||
AnchorStoreSaveSceneToJson = reinterpret_cast<decltype(AnchorStoreSaveSceneToJson)>(LoadFunction(TEXT("AnchorStoreSaveSceneToJson")));
|
||||
AnchorStoreFreeJson = reinterpret_cast<decltype(AnchorStoreFreeJson)>(LoadFunction(TEXT("AnchorStoreFreeJson")));
|
||||
AnchorStoreClearRooms = reinterpret_cast<decltype(AnchorStoreClearRooms)>(LoadFunction(TEXT("AnchorStoreClearRooms")));
|
||||
AnchorStoreClearRoom = reinterpret_cast<decltype(AnchorStoreClearRoom)>(LoadFunction(TEXT("AnchorStoreClearRoom")));
|
||||
AnchorStoreOnOpenXrEvent = reinterpret_cast<decltype(AnchorStoreOnOpenXrEvent)>(LoadFunction(TEXT("AnchorStoreOnOpenXrEvent")));
|
||||
AnchorStoreTick = reinterpret_cast<decltype(AnchorStoreTick)>(LoadFunction(TEXT("AnchorStoreTick")));
|
||||
AnchorStoreRegisterEventListener = reinterpret_cast<decltype(AnchorStoreRegisterEventListener)>(LoadFunction(TEXT("AnchorStoreRegisterEventListener")));
|
||||
AnchorStoreRaycastRoom = reinterpret_cast<decltype(AnchorStoreRaycastRoom)>(LoadFunction(TEXT("AnchorStoreRaycastRoom")));
|
||||
AnchorStoreRaycastRoomAll = reinterpret_cast<decltype(AnchorStoreRaycastRoomAll)>(LoadFunction(TEXT("AnchorStoreRaycastRoomAll")));
|
||||
AnchorStoreIsDiscoveryRunning = reinterpret_cast<decltype(AnchorStoreIsDiscoveryRunning)>(LoadFunction(TEXT("AnchorStoreIsDiscoveryRunning")));
|
||||
AddVectors = reinterpret_cast<decltype(AddVectors)>(LoadFunction(TEXT("AddVectors")));
|
||||
TriangulatePolygon = reinterpret_cast<decltype(TriangulatePolygon)>(LoadFunction(TEXT("TriangulatePolygon")));
|
||||
FreeMesh = reinterpret_cast<decltype(FreeMesh)>(LoadFunction(TEXT("FreeMesh")));
|
||||
ComputeMeshSegmentation = reinterpret_cast<decltype(ComputeMeshSegmentation)>(LoadFunction(TEXT("ComputeMeshSegmentation")));
|
||||
FreeMeshSegmentation = reinterpret_cast<decltype(FreeMeshSegmentation)>(LoadFunction(TEXT("FreeMeshSegmentation")));
|
||||
}
|
||||
|
||||
void UnloadNativeFunctions()
|
||||
{
|
||||
SetLogPrinter = nullptr;
|
||||
AnchorStoreCreate = nullptr;
|
||||
AnchorStoreCreateWithoutOpenXr = nullptr;
|
||||
AnchorStoreDestroy = nullptr;
|
||||
AnchorStoreSetBaseSpace = nullptr;
|
||||
AnchorStoreStartDiscovery = nullptr;
|
||||
AnchorStoreLoadSceneFromJson = nullptr;
|
||||
AnchorStoreSaveSceneToJson = nullptr;
|
||||
AnchorStoreFreeJson = nullptr;
|
||||
AnchorStoreClearRooms = nullptr;
|
||||
AnchorStoreClearRoom = nullptr;
|
||||
AnchorStoreOnOpenXrEvent = nullptr;
|
||||
AnchorStoreTick = nullptr;
|
||||
AnchorStoreRegisterEventListener = nullptr;
|
||||
AnchorStoreRaycastRoom = nullptr;
|
||||
AnchorStoreRaycastRoomAll = nullptr;
|
||||
AnchorStoreIsDiscoveryRunning = nullptr;
|
||||
AddVectors = nullptr;
|
||||
TriangulatePolygon = nullptr;
|
||||
FreeMesh = nullptr;
|
||||
ComputeMeshSegmentation = nullptr;
|
||||
FreeMeshSegmentation = nullptr;
|
||||
}
|
||||
|
||||
void* LoadFunction(const TCHAR* ProcName);
|
||||
|
||||
static MRUKShared* Instance;
|
||||
void* MRUKSharedHandle;
|
||||
|
||||
MRUKShared(void* handle);
|
||||
~MRUKShared();
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKit.h"
|
||||
#include "Interfaces/IPluginManager.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "ShaderCore.h"
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "ISettingsModule.h"
|
||||
#endif // WITH_EDITOR
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FMRUKModule"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogMRUK);
|
||||
|
||||
const FString FMRUKLabels::Floor("FLOOR");
|
||||
const FString FMRUKLabels::WallFace("WALL_FACE");
|
||||
const FString FMRUKLabels::InvisibleWallFace("INVISIBLE_WALL_FACE");
|
||||
const FString FMRUKLabels::Ceiling("CEILING");
|
||||
const FString FMRUKLabels::DoorFrame("DOOR_FRAME");
|
||||
const FString FMRUKLabels::WindowFrame("WINDOW_FRAME");
|
||||
const FString FMRUKLabels::Couch("COUCH");
|
||||
const FString FMRUKLabels::Table("TABLE");
|
||||
const FString FMRUKLabels::Screen("SCREEN");
|
||||
const FString FMRUKLabels::Bed("BED");
|
||||
const FString FMRUKLabels::Lamp("LAMP");
|
||||
const FString FMRUKLabels::Plant("PLANT");
|
||||
const FString FMRUKLabels::Storage("STORAGE");
|
||||
const FString FMRUKLabels::WallArt("WALL_ART");
|
||||
const FString FMRUKLabels::GlobalMesh("GLOBAL_MESH");
|
||||
const FString FMRUKLabels::Other("OTHER");
|
||||
|
||||
bool FMRUKLabelFilter::PassesFilter(const TArray<FString>& Labels) const
|
||||
{
|
||||
for (const auto& ExcludedLabel : ExcludedLabels)
|
||||
{
|
||||
if (Labels.Contains(ExcludedLabel))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto& IncludedLabel : IncludedLabels)
|
||||
{
|
||||
if (Labels.Contains(IncludedLabel))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return IncludedLabels.IsEmpty();
|
||||
}
|
||||
|
||||
UMRUKSettings::UMRUKSettings(const FObjectInitializer& obj)
|
||||
{
|
||||
}
|
||||
|
||||
void FMRUKModule::StartupModule()
|
||||
{
|
||||
// This code will execute after your module is loaded into memory; the exact timing is specified
|
||||
// in the .uplugin file per-module
|
||||
#if WITH_EDITOR
|
||||
if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings"))
|
||||
{
|
||||
SettingsModule->RegisterSettings("Project", "Plugins", "MRUtilityKit",
|
||||
LOCTEXT("RuntimeSettingsName", "Mixed Reality Utility Kit"), LOCTEXT("RuntimeSettingsDescription", "Configure the Mixed Reality Utility plugin"),
|
||||
GetMutableDefault<UMRUKSettings>());
|
||||
}
|
||||
#endif // WITH_EDITOR
|
||||
}
|
||||
|
||||
void FMRUKModule::ShutdownModule()
|
||||
{
|
||||
// This function may be called during shutdown to clean up your module. For modules that support
|
||||
// dynamic reloading, we call this function before unloading the module.
|
||||
#if WITH_EDITOR
|
||||
if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings"))
|
||||
{
|
||||
SettingsModule->UnregisterSettings("Project", "Plugins", "MRUtilityKit");
|
||||
}
|
||||
#endif // WITH_EDITOR
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FMRUKModule, MRUtilityKit)
|
||||
@@ -0,0 +1,889 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitAnchor.h"
|
||||
#include "MRUtilityKit.h"
|
||||
#include "MRUtilityKitBPLibrary.h"
|
||||
#include "MRUtilityKitGeometry.h"
|
||||
#include "MRUtilityKitSubsystem.h"
|
||||
#include "MRUtilityKitSerializationHelpers.h"
|
||||
#include "MRUtilityKitSeatsComponent.h"
|
||||
#include "MRUtilityKitRoom.h"
|
||||
#include "OculusXRAnchorTypes.h"
|
||||
#include "Engine/World.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "MRUKAnchor"
|
||||
|
||||
// #pragma optimize("", off)
|
||||
|
||||
AMRUKAnchor::AMRUKAnchor(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
// Create a scene component as root so we can attach spawned actors to it
|
||||
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("SceneComponent"));
|
||||
}
|
||||
|
||||
bool AMRUKAnchor::LoadFromData(UMRUKAnchorData* AnchorData)
|
||||
{
|
||||
check(AnchorData);
|
||||
|
||||
bool Changed = false;
|
||||
|
||||
if (const auto Seat = GetComponentByClass<UMRUKSeatsComponent>(); Seat && !HasLabel(FMRUKLabels::Couch))
|
||||
{
|
||||
Seat->UnregisterComponent();
|
||||
Seat->DestroyComponent();
|
||||
Changed = true;
|
||||
}
|
||||
|
||||
AnchorUUID = AnchorData->SpaceQuery.UUID;
|
||||
SpaceHandle = AnchorData->SpaceQuery.Space;
|
||||
|
||||
SetActorTransform(AnchorData->Transform, false, nullptr, ETeleportType::ResetPhysics);
|
||||
const auto NewSemanticClassifications = AnchorData->SemanticClassifications;
|
||||
if (NewSemanticClassifications != SemanticClassifications)
|
||||
{
|
||||
Changed = true;
|
||||
}
|
||||
SemanticClassifications = NewSemanticClassifications;
|
||||
|
||||
const FString Semantics = FString::Join(SemanticClassifications, TEXT("-"));
|
||||
UE_LOG(LogMRUK, Log, TEXT("SpatialAnchor label is %s"), *Semantics);
|
||||
|
||||
if (PlaneBounds != AnchorData->PlaneBounds)
|
||||
{
|
||||
Changed = true;
|
||||
}
|
||||
PlaneBounds = AnchorData->PlaneBounds;
|
||||
PlaneBoundary2D = AnchorData->PlaneBoundary2D;
|
||||
|
||||
if (VolumeBounds != AnchorData->VolumeBounds)
|
||||
{
|
||||
Changed = true;
|
||||
}
|
||||
VolumeBounds = AnchorData->VolumeBounds;
|
||||
|
||||
if (Changed)
|
||||
{
|
||||
if (ProceduralMeshComponent)
|
||||
{
|
||||
ProceduralMeshComponent->UnregisterComponent();
|
||||
ProceduralMeshComponent->DestroyComponent();
|
||||
ProceduralMeshComponent = nullptr;
|
||||
}
|
||||
|
||||
if (CachedMesh.IsSet())
|
||||
{
|
||||
CachedMesh.GetValue().Clear();
|
||||
}
|
||||
}
|
||||
|
||||
return Changed;
|
||||
}
|
||||
|
||||
bool AMRUKAnchor::IsPositionInBoundary(const FVector2D& Position)
|
||||
{
|
||||
if (PlaneBoundary2D.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int Intersections = 0;
|
||||
|
||||
for (int i = 1; i <= PlaneBoundary2D.Num(); i++)
|
||||
{
|
||||
const FVector2D P1 = PlaneBoundary2D[i - 1];
|
||||
const FVector2D P2 = PlaneBoundary2D[i % PlaneBoundary2D.Num()];
|
||||
if (Position.Y > FMath::Min(P1.Y, P2.Y) && Position.Y <= FMath::Max(P1.Y, P2.Y))
|
||||
{
|
||||
if (Position.X <= FMath::Max(P1.X, P2.X))
|
||||
{
|
||||
if (P1.Y != P2.Y)
|
||||
{
|
||||
const auto Frac = (Position.Y - P1.Y) / (P2.Y - P1.Y);
|
||||
const auto XIntersection = P1.X + Frac * (P2.X - P1.X);
|
||||
if (P1.X == P2.X || Position.X <= XIntersection)
|
||||
{
|
||||
Intersections++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Intersections % 2 == 1;
|
||||
}
|
||||
|
||||
FVector AMRUKAnchor::GenerateRandomPositionOnPlane()
|
||||
{
|
||||
return GenerateRandomPositionOnPlaneFromStream(FRandomStream(NAME_None));
|
||||
}
|
||||
|
||||
FVector AMRUKAnchor::GenerateRandomPositionOnPlaneFromStream(const FRandomStream& RandomStream)
|
||||
{
|
||||
if (PlaneBoundary2D.IsEmpty())
|
||||
{
|
||||
return FVector::ZeroVector;
|
||||
}
|
||||
|
||||
// Cache the mesh so that if the function is called multiple times it will re-use the previously triangulated mesh.
|
||||
if (!CachedMesh.IsSet())
|
||||
{
|
||||
TriangulatedMeshCache Mesh;
|
||||
|
||||
TArray<FVector2f> PlaneBoundary;
|
||||
PlaneBoundary.Reserve(PlaneBoundary2D.Num());
|
||||
for (const auto Point : PlaneBoundary2D)
|
||||
{
|
||||
PlaneBoundary.Push(FVector2f(Point));
|
||||
}
|
||||
|
||||
MRUKTriangulatePolygon({ PlaneBoundary }, Mesh.Vertices, Mesh.Triangles);
|
||||
|
||||
// Compute the area of each triangle and the total surface area of the mesh
|
||||
Mesh.Areas.Reserve(Mesh.Triangles.Num() / 3);
|
||||
Mesh.TotalArea = 0.0f;
|
||||
for (int i = 0; i < Mesh.Triangles.Num(); i += 3)
|
||||
{
|
||||
const auto I0 = Mesh.Triangles[i];
|
||||
const auto I1 = Mesh.Triangles[i + 1];
|
||||
const auto I2 = Mesh.Triangles[i + 2];
|
||||
auto V0 = Mesh.Vertices[I0];
|
||||
auto V1 = Mesh.Vertices[I1];
|
||||
auto V2 = Mesh.Vertices[I2];
|
||||
const auto Cross = FVector2D::CrossProduct(V1 - V0, V2 - V0);
|
||||
float Area = Cross * 0.5f;
|
||||
Mesh.TotalArea += Area;
|
||||
Mesh.Areas.Add(Area);
|
||||
}
|
||||
CachedMesh.Emplace(MoveTemp(Mesh));
|
||||
}
|
||||
|
||||
const auto& [Vertices, Triangles, Areas, TotalArea] = CachedMesh.GetValue();
|
||||
|
||||
// Pick a random triangle weighted by surface area (triangles with larger surface
|
||||
// area have more chance of being chosen)
|
||||
auto Rand = RandomStream.FRandRange(0.0f, TotalArea);
|
||||
int TriangleIndex = 0;
|
||||
for (; TriangleIndex < Areas.Num() - 1; ++TriangleIndex)
|
||||
{
|
||||
Rand -= Areas[TriangleIndex];
|
||||
if (Rand <= 0.0f)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the vertices of the chosen triangle
|
||||
const auto I0 = Triangles[TriangleIndex * 3];
|
||||
const auto I1 = Triangles[TriangleIndex * 3 + 1];
|
||||
const auto I2 = Triangles[TriangleIndex * 3 + 2];
|
||||
const auto V0 = FVector(0.0, Vertices[I0].X, Vertices[I0].Y);
|
||||
const auto V1 = FVector(0.0, Vertices[I1].X, Vertices[I1].Y);
|
||||
const auto V2 = FVector(0.0, Vertices[I2].X, Vertices[I2].Y);
|
||||
|
||||
// Calculate a random point on that triangle
|
||||
float U = RandomStream.FRandRange(0.0f, 1.0f);
|
||||
float V = RandomStream.FRandRange(0.0f, 1.0f);
|
||||
if (U + V > 1.0f)
|
||||
{
|
||||
if (U > V)
|
||||
{
|
||||
U = 1.0f - U;
|
||||
}
|
||||
else
|
||||
{
|
||||
V = 1.0f - V;
|
||||
}
|
||||
}
|
||||
return V0 + U * (V1 - V0) + V * (V2 - V0);
|
||||
}
|
||||
|
||||
bool AMRUKAnchor::Raycast(const FVector& Origin, const FVector& Direction, float MaxDist, FMRUKHit& OutHit, int32 ComponentTypes)
|
||||
{
|
||||
// If this anchor is the global mesh test against it
|
||||
if ((ComponentTypes & static_cast<int32>(EMRUKComponentType::Mesh)) != 0 && this == Room->GlobalMeshAnchor)
|
||||
{
|
||||
FHitResult GlobalMeshOutHit{};
|
||||
float Dist = MaxDist;
|
||||
if (MaxDist <= 0.0)
|
||||
{
|
||||
const float WorldToMeters = GetWorld()->GetWorldSettings()->WorldToMeters;
|
||||
Dist = WorldToMeters * 1024; // 1024 m should cover every scene
|
||||
}
|
||||
if (ActorLineTraceSingle(GlobalMeshOutHit, Origin, Origin + Direction * Dist, ECollisionChannel::ECC_WorldDynamic, FCollisionQueryParams::DefaultQueryParam))
|
||||
{
|
||||
OutHit.HitPosition = GlobalMeshOutHit.Location;
|
||||
OutHit.HitNormal = GlobalMeshOutHit.Normal;
|
||||
OutHit.HitDistance = GlobalMeshOutHit.Distance;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Transform = GetTransform();
|
||||
// Transform the ray into local space
|
||||
auto InverseTransform = Transform.Inverse();
|
||||
const auto OriginLocal = InverseTransform.TransformPositionNoScale(Origin);
|
||||
const auto DirectionLocal = InverseTransform.TransformVectorNoScale(Direction);
|
||||
FRay LocalRay = FRay(OriginLocal, DirectionLocal);
|
||||
bool FoundHit = false;
|
||||
|
||||
// If this anchor has a plane, hit test against it
|
||||
if ((ComponentTypes & static_cast<int32>(EMRUKComponentType::Plane)) != 0 && PlaneBounds.bIsValid && RayCastPlane(LocalRay, MaxDist, OutHit))
|
||||
{
|
||||
// Update max dist for the volume raycast
|
||||
MaxDist = OutHit.HitDistance;
|
||||
FoundHit = true;
|
||||
}
|
||||
// If this anchor has a volume, hit test against it
|
||||
if ((ComponentTypes & static_cast<int32>(EMRUKComponentType::Volume)) != 0 && VolumeBounds.IsValid && RayCastVolume(LocalRay, MaxDist, OutHit))
|
||||
{
|
||||
MaxDist = OutHit.HitDistance;
|
||||
FoundHit = true;
|
||||
}
|
||||
|
||||
return FoundHit;
|
||||
}
|
||||
|
||||
bool AMRUKAnchor::RaycastAll(const FVector& Origin, const FVector& Direction, float MaxDist, TArray<FMRUKHit>& OutHits, int32 ComponentTypes)
|
||||
{
|
||||
if ((ComponentTypes & static_cast<int32>(EMRUKComponentType::Mesh)) != 0 && this == Room->GlobalMeshAnchor)
|
||||
{
|
||||
FHitResult GlobalMeshOutHit{};
|
||||
float Dist = MaxDist;
|
||||
if (MaxDist <= 0.0)
|
||||
{
|
||||
|
||||
const float WorldToMeters = GetWorld()->GetWorldSettings()->WorldToMeters;
|
||||
Dist = WorldToMeters * 1024; // 1024 m should cover every scene
|
||||
}
|
||||
if (ActorLineTraceSingle(GlobalMeshOutHit, Origin, Origin + Direction * Dist, ECollisionChannel::ECC_WorldDynamic, FCollisionQueryParams::DefaultQueryParam))
|
||||
{
|
||||
FMRUKHit Hit{};
|
||||
Hit.HitPosition = GlobalMeshOutHit.Location;
|
||||
Hit.HitNormal = GlobalMeshOutHit.Normal;
|
||||
Hit.HitDistance = GlobalMeshOutHit.Distance;
|
||||
OutHits.Push(Hit);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Transform = GetTransform();
|
||||
// Transform the ray into local space
|
||||
auto InverseTransform = Transform.Inverse();
|
||||
const auto OriginLocal = InverseTransform.TransformPositionNoScale(Origin);
|
||||
const auto DirectionLocal = InverseTransform.TransformVectorNoScale(Direction);
|
||||
FRay LocalRay = FRay(OriginLocal, DirectionLocal);
|
||||
bool FoundHit = false;
|
||||
// If this anchor has a plane, hit test against it
|
||||
FMRUKHit Hit;
|
||||
if ((ComponentTypes & static_cast<int32>(EMRUKComponentType::Plane)) != 0 && PlaneBounds.bIsValid && RayCastPlane(LocalRay, MaxDist, Hit))
|
||||
{
|
||||
OutHits.Push(Hit);
|
||||
FoundHit = true;
|
||||
}
|
||||
// If this anchor has a volume, hit test against it
|
||||
if ((ComponentTypes & static_cast<int32>(EMRUKComponentType::Volume)) != 0 && VolumeBounds.IsValid && RayCastVolume(LocalRay, MaxDist, Hit))
|
||||
{
|
||||
OutHits.Push(Hit);
|
||||
FoundHit = true;
|
||||
}
|
||||
|
||||
return FoundHit;
|
||||
}
|
||||
|
||||
void AMRUKAnchor::AttachProceduralMesh(const TArray<FString>& CutHoleLabels, bool GenerateCollision, UMaterialInterface* ProceduralMaterial)
|
||||
{
|
||||
AttachProceduralMesh({}, CutHoleLabels, GenerateCollision, ProceduralMaterial);
|
||||
}
|
||||
|
||||
void AMRUKAnchor::AttachProceduralMesh(TArray<FMRUKPlaneUV> PlaneUVAdjustments, const TArray<FString>& CutHoleLabels, bool GenerateCollision, UMaterialInterface* ProceduralMaterial)
|
||||
{
|
||||
if (ProceduralMeshComponent)
|
||||
{
|
||||
// Procedural mesh already attached
|
||||
return;
|
||||
}
|
||||
|
||||
ProceduralMeshComponent = NewObject<UProceduralMeshComponent>(this, TEXT("ProceduralMesh"));
|
||||
ProceduralMeshComponent->SetupAttachment(RootComponent);
|
||||
ProceduralMeshComponent->RegisterComponent();
|
||||
|
||||
GenerateProceduralAnchorMesh(ProceduralMeshComponent, PlaneUVAdjustments, CutHoleLabels, false, GenerateCollision);
|
||||
|
||||
for (int32 SectionIndex = 0; SectionIndex < ProceduralMeshComponent->GetNumSections(); ++SectionIndex)
|
||||
{
|
||||
ProceduralMeshComponent->SetMaterial(SectionIndex, ProceduralMaterial);
|
||||
}
|
||||
}
|
||||
|
||||
void AMRUKAnchor::GenerateProceduralAnchorMesh(UProceduralMeshComponent* ProceduralMesh, const TArray<FMRUKPlaneUV>& PlaneUVAdjustments, const TArray<FString>& CutHoleLabels, bool PreferVolume, bool GenerateCollision, double Offset)
|
||||
{
|
||||
int SectionIndex = 0;
|
||||
if (VolumeBounds.IsValid)
|
||||
{
|
||||
TArray<FVector> Vertices;
|
||||
TArray<int32> Triangles;
|
||||
TArray<FVector> Normals;
|
||||
TArray<FVector2D> UVs;
|
||||
TArray<FLinearColor> Colors; // Currently unused
|
||||
TArray<FProcMeshTangent> Tangents; // Currently unused
|
||||
constexpr int32 NumVertices = 24;
|
||||
constexpr int32 NumTriangles = 12;
|
||||
Vertices.Reserve(NumVertices);
|
||||
Triangles.Reserve(3 * NumTriangles);
|
||||
Normals.Reserve(NumVertices);
|
||||
UVs.Reserve(NumVertices);
|
||||
|
||||
FBox VolumeBoundsOffset(VolumeBounds.Min - Offset, VolumeBounds.Max + Offset);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
for (int j = 0; j < 2; j++)
|
||||
{
|
||||
FVector Normal = FVector::ZeroVector;
|
||||
if (j == 0)
|
||||
{
|
||||
Normal[i] = -1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
Normal[i] = 1.0f;
|
||||
}
|
||||
auto BaseIndex = Vertices.Num();
|
||||
FVector Vertex;
|
||||
Vertex[i] = VolumeBoundsOffset[j][i];
|
||||
for (int k = 0; k < 2; k++)
|
||||
{
|
||||
for (int l = 0; l < 2; l++)
|
||||
{
|
||||
Vertex[(i + 1) % 3] = VolumeBoundsOffset[k][(i + 1) % 3];
|
||||
Vertex[(i + 2) % 3] = VolumeBoundsOffset[l][(i + 2) % 3];
|
||||
Vertices.Push(Vertex);
|
||||
Normals.Push(Normal);
|
||||
// The 4 side faces of the cube should have their 0, 0 at the top left corner
|
||||
// when viewed from the outside.
|
||||
// The top face should have UVs that are consistent with planes to avoid Z fighting
|
||||
// in case a plane and volume overlap (e.g. in the case of the desk).
|
||||
FVector2D UV;
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
UV = FVector2D(1 - k, 1 - l);
|
||||
break;
|
||||
case 1:
|
||||
UV = FVector2D(k, l);
|
||||
break;
|
||||
case 2:
|
||||
UV = FVector2D(1 - l, k);
|
||||
break;
|
||||
default:
|
||||
UV = FVector2D::Zero();
|
||||
ensure(0);
|
||||
}
|
||||
if (j == 0)
|
||||
{
|
||||
UV.X = 1 - UV.X;
|
||||
}
|
||||
UVs.Push(UV);
|
||||
}
|
||||
}
|
||||
if (j == 1)
|
||||
{
|
||||
Triangles.Push(BaseIndex);
|
||||
Triangles.Push(BaseIndex + 1);
|
||||
Triangles.Push(BaseIndex + 2);
|
||||
Triangles.Push(BaseIndex + 2);
|
||||
Triangles.Push(BaseIndex + 1);
|
||||
Triangles.Push(BaseIndex + 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
Triangles.Push(BaseIndex);
|
||||
Triangles.Push(BaseIndex + 2);
|
||||
Triangles.Push(BaseIndex + 1);
|
||||
Triangles.Push(BaseIndex + 1);
|
||||
Triangles.Push(BaseIndex + 2);
|
||||
Triangles.Push(BaseIndex + 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProceduralMesh->CreateMeshSection_LinearColor(SectionIndex++, Vertices, Triangles, Normals, UVs, Colors, Tangents, GenerateCollision);
|
||||
}
|
||||
if (PlaneBounds.bIsValid && !(VolumeBounds.IsValid && PreferVolume))
|
||||
{
|
||||
TArray<TArray<FVector2f>> Polygons;
|
||||
|
||||
TArray<FVector2f> PlaneBoundary;
|
||||
PlaneBoundary.Reserve(PlaneBoundary2D.Num());
|
||||
for (const auto Point : PlaneBoundary2D)
|
||||
{
|
||||
PlaneBoundary.Push(FVector2f(Point));
|
||||
}
|
||||
Polygons.Push(PlaneBoundary);
|
||||
|
||||
if (!CutHoleLabels.IsEmpty())
|
||||
{
|
||||
for (const auto& ChildAnchor : ChildAnchors)
|
||||
{
|
||||
if (!ChildAnchor->HasAnyLabel(CutHoleLabels))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ChildAnchor->PlaneBounds.bIsValid)
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Can only cut holes with anchors that have a plane"));
|
||||
continue;
|
||||
}
|
||||
|
||||
const FVector ChildPositionLS = GetActorTransform().InverseTransformPosition(ChildAnchor->GetActorLocation());
|
||||
TArray<FVector2f> HoleBoundary;
|
||||
HoleBoundary.Reserve(ChildAnchor->PlaneBoundary2D.Num());
|
||||
for (int32 I = ChildAnchor->PlaneBoundary2D.Num() - 1; I >= 0; --I)
|
||||
{
|
||||
HoleBoundary.Push(FVector2f(ChildPositionLS.Y, ChildPositionLS.Z) + FVector2f(ChildAnchor->PlaneBoundary2D[I]));
|
||||
}
|
||||
Polygons.Push(HoleBoundary);
|
||||
}
|
||||
}
|
||||
|
||||
TArray<FVector2D> MeshVertices;
|
||||
TArray<int32> MeshIndices;
|
||||
MRUKTriangulatePolygon(Polygons, MeshVertices, MeshIndices);
|
||||
|
||||
TArray<FVector> Vertices;
|
||||
TArray<FVector> Normals;
|
||||
TArray<FVector2D> UV0s;
|
||||
TArray<FVector2D> UV1s;
|
||||
TArray<FVector2D> UV2s;
|
||||
TArray<FVector2D> UV3s;
|
||||
TArray<FLinearColor> Colors; // Currently unused
|
||||
TArray<FProcMeshTangent> Tangents;
|
||||
const int32 NumVertices = MeshVertices.Num();
|
||||
Normals.Reserve(NumVertices);
|
||||
UV0s.Reserve(NumVertices);
|
||||
UV1s.Reserve(NumVertices);
|
||||
UV2s.Reserve(NumVertices);
|
||||
UV3s.Reserve(NumVertices);
|
||||
Tangents.Reserve(NumVertices);
|
||||
|
||||
static const FVector Normal = -FVector::XAxisVector;
|
||||
const FVector NormalOffset = Normal * Offset;
|
||||
auto BoundsSize = PlaneBounds.GetSize();
|
||||
for (const auto& PlaneBoundaryVertex : MeshVertices)
|
||||
{
|
||||
const FVector Vertex = FVector(0, PlaneBoundaryVertex.X, PlaneBoundaryVertex.Y) + NormalOffset;
|
||||
Vertices.Push(Vertex);
|
||||
Normals.Push(Normal);
|
||||
Tangents.Push(FProcMeshTangent(-FVector::YAxisVector, false));
|
||||
auto U = (PlaneBoundaryVertex.X - PlaneBounds.Min.X) / BoundsSize.X;
|
||||
auto V = 1 - (PlaneBoundaryVertex.Y - PlaneBounds.Min.Y) / BoundsSize.Y;
|
||||
if (PlaneUVAdjustments.Num() == 0)
|
||||
{
|
||||
UV0s.Push(FVector2D(U, V));
|
||||
}
|
||||
if (PlaneUVAdjustments.Num() >= 1)
|
||||
{
|
||||
UV0s.Push(FVector2D(U, V) * PlaneUVAdjustments[0].Scale + PlaneUVAdjustments[0].Offset);
|
||||
}
|
||||
if (PlaneUVAdjustments.Num() >= 2)
|
||||
{
|
||||
UV1s.Push(FVector2D(U, V) * PlaneUVAdjustments[1].Scale + PlaneUVAdjustments[1].Offset);
|
||||
}
|
||||
if (PlaneUVAdjustments.Num() >= 3)
|
||||
{
|
||||
UV2s.Push(FVector2D(U, V) * PlaneUVAdjustments[2].Scale + PlaneUVAdjustments[2].Offset);
|
||||
}
|
||||
if (PlaneUVAdjustments.Num() >= 4)
|
||||
{
|
||||
UV3s.Push(FVector2D(U, V) * PlaneUVAdjustments[3].Scale + PlaneUVAdjustments[3].Offset);
|
||||
}
|
||||
}
|
||||
ProceduralMesh->CreateMeshSection_LinearColor(SectionIndex++, Vertices, MeshIndices, Normals, UV0s, UV1s, UV2s, UV3s, Colors, Tangents, GenerateCollision);
|
||||
}
|
||||
}
|
||||
|
||||
bool AMRUKAnchor::HasLabel(const FString& Label) const
|
||||
{
|
||||
return SemanticClassifications.Contains(Label);
|
||||
}
|
||||
|
||||
bool AMRUKAnchor::HasAnyLabel(const TArray<FString>& Labels) const
|
||||
{
|
||||
for (const auto& Label : Labels)
|
||||
{
|
||||
if (HasLabel(Label))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AMRUKAnchor::PassesLabelFilter(const FMRUKLabelFilter& LabelFilter) const
|
||||
{
|
||||
return LabelFilter.PassesFilter(SemanticClassifications);
|
||||
}
|
||||
|
||||
double AMRUKAnchor::GetClosestSurfacePosition(const FVector& TestPosition, FVector& OutSurfacePosition)
|
||||
{
|
||||
const auto& Transform = GetActorTransform();
|
||||
const auto TestPositionLocal = Transform.InverseTransformPosition(TestPosition);
|
||||
|
||||
double ClosestDistance = DBL_MAX;
|
||||
FVector ClosestPoint = FVector::ZeroVector;
|
||||
|
||||
if (PlaneBounds.bIsValid)
|
||||
{
|
||||
const auto BestPoint2D = PlaneBounds.GetClosestPointTo(FVector2D(TestPositionLocal.Y, TestPositionLocal.Z));
|
||||
const FVector BestPoint(0.0, BestPoint2D.X, BestPoint2D.Y);
|
||||
const auto Distance = FVector::Distance(BestPoint, TestPositionLocal);
|
||||
if (Distance < ClosestDistance)
|
||||
{
|
||||
ClosestPoint = BestPoint;
|
||||
ClosestDistance = Distance;
|
||||
}
|
||||
}
|
||||
if (VolumeBounds.IsValid)
|
||||
{
|
||||
const auto BestPoint = VolumeBounds.GetClosestPointTo(TestPositionLocal);
|
||||
const auto Distance = FVector::Distance(BestPoint, TestPositionLocal);
|
||||
if (Distance < ClosestDistance)
|
||||
{
|
||||
ClosestPoint = BestPoint;
|
||||
ClosestDistance = Distance;
|
||||
}
|
||||
}
|
||||
|
||||
OutSurfacePosition = Transform.TransformPosition(ClosestPoint);
|
||||
return ClosestDistance;
|
||||
}
|
||||
|
||||
bool AMRUKAnchor::IsPositionInVolumeBounds(const FVector& Position, bool TestVerticalBounds, double Tolerance)
|
||||
{
|
||||
if (!VolumeBounds.IsValid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& LocalPosition = GetActorTransform().InverseTransformPosition(Position);
|
||||
|
||||
return ((TestVerticalBounds ? ((LocalPosition.X >= VolumeBounds.Min.X - Tolerance) && (LocalPosition.X <= VolumeBounds.Max.X + Tolerance)) : true)
|
||||
&& (LocalPosition.Y >= VolumeBounds.Min.Y - Tolerance) && (LocalPosition.Y <= VolumeBounds.Max.Y + Tolerance)
|
||||
&& (LocalPosition.Z >= VolumeBounds.Min.Z - Tolerance) && (LocalPosition.Z <= VolumeBounds.Max.Z + Tolerance));
|
||||
}
|
||||
|
||||
FVector AMRUKAnchor::GetFacingDirection() const
|
||||
{
|
||||
if (Room == nullptr)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!VolumeBounds.IsValid)
|
||||
{
|
||||
return GetActorForwardVector();
|
||||
}
|
||||
|
||||
int32 CardinalAxis = 0;
|
||||
return UMRUKBPLibrary::ComputeDirectionAwayFromClosestWall(this, CardinalAxis, {});
|
||||
}
|
||||
|
||||
AActor* AMRUKAnchor::SpawnInterior(const TSubclassOf<class AActor>& ActorClass, bool MatchAspectRatio, bool CalculateFacingDirection, EMRUKSpawnerScalingMode ScalingMode)
|
||||
{
|
||||
Interior = GetWorld()->SpawnActor(ActorClass);
|
||||
auto InteriorRoot = Interior->GetRootComponent();
|
||||
if (!InteriorRoot)
|
||||
{
|
||||
UE_LOG(LogMRUK, Error, TEXT("SpawnInterior Spawned actor does not have a root component."));
|
||||
return nullptr;
|
||||
}
|
||||
InteriorRoot->SetMobility(EComponentMobility::Movable);
|
||||
Interior->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
|
||||
Interior->SetActorRelativeScale3D(FVector::OneVector);
|
||||
|
||||
const auto ChildLocalBounds = Interior->CalculateComponentsBoundingBoxInLocalSpace(true);
|
||||
FQuat Rotation = FQuat::Identity;
|
||||
FVector Offset = FVector::ZeroVector;
|
||||
FVector Scale = FVector::OneVector;
|
||||
|
||||
if (VolumeBounds.IsValid)
|
||||
{
|
||||
int CardinalAxisIndex = 0;
|
||||
if (CalculateFacingDirection && !MatchAspectRatio)
|
||||
{
|
||||
// Pick rotation that is pointing away from the closest wall
|
||||
// If we are also matching the aspect ratio then we only have a choice
|
||||
// between 2 directions and first need to figure out what those 2 directions
|
||||
// are before doing the ray casting.
|
||||
UMRUKBPLibrary::ComputeDirectionAwayFromClosestWall(this, CardinalAxisIndex, {});
|
||||
}
|
||||
Rotation = FQuat::MakeFromEuler(FVector(90, -(CardinalAxisIndex + 1) * 90, 90));
|
||||
|
||||
FBox ChildBounds = ChildLocalBounds.TransformBy(FTransform(Rotation));
|
||||
const FVector ChildSize1 = ChildBounds.GetSize();
|
||||
|
||||
Scale = VolumeBounds.GetSize() / ChildSize1;
|
||||
|
||||
if (MatchAspectRatio)
|
||||
{
|
||||
FVector ChildSize2 = ChildSize1;
|
||||
Swap(ChildSize2.Y, ChildSize2.Z);
|
||||
FVector Scale2 = VolumeBounds.GetSize() / ChildSize2;
|
||||
|
||||
float Distortion1 = FMath::Max(Scale.Y, Scale.Z) / FMath::Min(Scale.Y, Scale.Z);
|
||||
float Distortion2 = FMath::Max(Scale2.Y, Scale2.Z) / FMath::Min(Scale2.Y, Scale2.Z);
|
||||
|
||||
bool FlipToMatchAspectRatio = Distortion1 > Distortion2;
|
||||
if (FlipToMatchAspectRatio)
|
||||
{
|
||||
CardinalAxisIndex = 1;
|
||||
Scale = Scale2;
|
||||
}
|
||||
if (CalculateFacingDirection)
|
||||
{
|
||||
UMRUKBPLibrary::ComputeDirectionAwayFromClosestWall(this, CardinalAxisIndex, FlipToMatchAspectRatio ? TArray<int>{ 0, 2 } : TArray<int>{ 1, 3 });
|
||||
}
|
||||
if (CardinalAxisIndex != 0)
|
||||
{
|
||||
// Update the rotation and child bounds if necessary
|
||||
Rotation = FQuat::MakeFromEuler(FVector(90, -(CardinalAxisIndex + 1) * 90, 90));
|
||||
ChildBounds = ChildLocalBounds.TransformBy(FTransform(Rotation));
|
||||
}
|
||||
}
|
||||
|
||||
switch (ScalingMode)
|
||||
{
|
||||
case EMRUKSpawnerScalingMode::UniformScaling:
|
||||
Scale.X = Scale.Y = Scale.Z = FMath::Min3(Scale.X, Scale.Y, Scale.Z);
|
||||
break;
|
||||
case EMRUKSpawnerScalingMode::UniformXYScale:
|
||||
Scale.Y = Scale.Z = FMath::Min(Scale.Y, Scale.Z);
|
||||
break;
|
||||
case EMRUKSpawnerScalingMode::NoScaling:
|
||||
Scale = FVector::OneVector;
|
||||
break;
|
||||
case EMRUKSpawnerScalingMode::Stretch:
|
||||
// Nothing to do
|
||||
break;
|
||||
}
|
||||
|
||||
// Calculate the offset between the base of the two bounding boxes. Note that the anchor is on the
|
||||
// top of the volume and the X axis points downwards. So the base is at Max.X.
|
||||
FVector VolumeBase = FVector(VolumeBounds.Max.X, 0.5 * (VolumeBounds.Min.Y + VolumeBounds.Max.Y), 0.5 * (VolumeBounds.Min.Z + VolumeBounds.Max.Z));
|
||||
FVector ChildBase = FVector(ChildBounds.Max.X, 0.5 * (ChildBounds.Min.Y + ChildBounds.Max.Y), 0.5 * (ChildBounds.Min.Z + ChildBounds.Max.Z));
|
||||
|
||||
Offset = VolumeBase - ChildBase * Scale;
|
||||
}
|
||||
else if (PlaneBounds.bIsValid)
|
||||
{
|
||||
const auto XAxis = GetTransform().GetUnitAxis(EAxis::X);
|
||||
// Adjust the rotation so that Z always points up. This enables assets to be authored in a more natural
|
||||
// way and show up in the scene as expected.
|
||||
if (XAxis.Z <= -UE_INV_SQRT_2)
|
||||
{
|
||||
// This is a floor or other surface facing upwards
|
||||
Rotation = FQuat::MakeFromEuler(FVector(0, 90, 0));
|
||||
}
|
||||
else if (XAxis.Z >= UE_INV_SQRT_2)
|
||||
{
|
||||
// This is ceiling or other surface facing downwards.
|
||||
Rotation = FQuat::MakeFromEuler(FVector(0, -90, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a wall or other upright surface.
|
||||
Rotation = FQuat::MakeFromEuler(FVector(0, 0, 180));
|
||||
}
|
||||
|
||||
const auto ChildBounds = ChildLocalBounds.TransformBy(FTransform(Rotation));
|
||||
const auto ChildBounds2D = FBox2D(FVector2D(ChildBounds.Min.Y, ChildBounds.Min.Z), FVector2D(ChildBounds.Max.Y, ChildBounds.Max.Z));
|
||||
auto Scale2D = PlaneBounds.GetSize() / ChildBounds2D.GetSize();
|
||||
|
||||
switch (ScalingMode)
|
||||
{
|
||||
case EMRUKSpawnerScalingMode::UniformScaling:
|
||||
case EMRUKSpawnerScalingMode::UniformXYScale:
|
||||
Scale2D.X = Scale2D.Y = FMath::Min(Scale2D.X, Scale2D.Y);
|
||||
break;
|
||||
case EMRUKSpawnerScalingMode::NoScaling:
|
||||
Scale2D = FVector2D::UnitVector;
|
||||
break;
|
||||
case EMRUKSpawnerScalingMode::Stretch:
|
||||
// Nothing to do
|
||||
break;
|
||||
}
|
||||
|
||||
const auto Offset2D = PlaneBounds.GetCenter() - ChildBounds2D.GetCenter() * Scale2D;
|
||||
|
||||
Offset = FVector(0.0, Offset2D.X, Offset2D.Y);
|
||||
Scale = FVector(0.5 * (Scale2D.X + Scale2D.Y), Scale2D.X, Scale2D.Y);
|
||||
}
|
||||
Interior->SetActorRelativeRotation(Rotation);
|
||||
Interior->SetActorRelativeLocation(Offset);
|
||||
UMRUKBPLibrary::SetScaleRecursivelyAdjustingForRotation(InteriorRoot, Scale);
|
||||
|
||||
return Interior;
|
||||
}
|
||||
|
||||
TSharedRef<FJsonObject> AMRUKAnchor::JsonSerialize()
|
||||
{
|
||||
TSharedRef<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
|
||||
JsonObject->SetField(TEXT("UUID"), MRUKSerialize(AnchorUUID));
|
||||
JsonObject->SetField(TEXT("SemanticClassifications"), MRUKSerialize(SemanticClassifications));
|
||||
JsonObject->SetField(TEXT("Transform"), MRUKSerialize(GetTransform()));
|
||||
if (PlaneBounds.bIsValid)
|
||||
{
|
||||
JsonObject->SetField(TEXT("PlaneBounds"), MRUKSerialize(PlaneBounds));
|
||||
}
|
||||
if (!PlaneBoundary2D.IsEmpty())
|
||||
{
|
||||
JsonObject->SetField(TEXT("PlaneBoundary2D"), MRUKSerialize(PlaneBoundary2D));
|
||||
}
|
||||
if (VolumeBounds.IsValid)
|
||||
{
|
||||
JsonObject->SetField(TEXT("VolumeBounds"), MRUKSerialize(VolumeBounds));
|
||||
}
|
||||
|
||||
if (this == Room->GlobalMeshAnchor)
|
||||
{
|
||||
TArray<UProceduralMeshComponent*> ProcMeshComponents;
|
||||
GetComponents<UProceduralMeshComponent>(ProcMeshComponents);
|
||||
for (const auto& Component : ProcMeshComponents)
|
||||
{
|
||||
const auto ProcMeshComponent = Cast<UProceduralMeshComponent>(Component);
|
||||
if (ProcMeshComponent && ProcMeshComponent->ComponentHasTag("GlobalMesh"))
|
||||
{
|
||||
ensure(ProcMeshComponent->GetNumSections() == 1);
|
||||
|
||||
auto GlobalMeshJson = MakeShared<FJsonObject>();
|
||||
GlobalMeshJson->SetField(TEXT("UUID"), MRUKSerialize(AnchorUUID));
|
||||
|
||||
const auto ProcMeshSection = ProcMeshComponent->GetProcMeshSection(0);
|
||||
|
||||
TArray<TSharedPtr<FJsonValue>> PositionsJson;
|
||||
for (const auto& Vertex : ProcMeshSection->ProcVertexBuffer)
|
||||
{
|
||||
PositionsJson.Add(MRUKSerialize(Vertex.Position));
|
||||
}
|
||||
GlobalMeshJson->SetArrayField(TEXT("Positions"), PositionsJson);
|
||||
|
||||
TArray<TSharedPtr<FJsonValue>> IndicesJson;
|
||||
for (const auto& Index : ProcMeshSection->ProcIndexBuffer)
|
||||
{
|
||||
IndicesJson.Add(MakeShared<FJsonValueNumber>(Index));
|
||||
}
|
||||
GlobalMeshJson->SetArrayField(TEXT("Indices"), IndicesJson);
|
||||
|
||||
JsonObject->SetObjectField(TEXT("GlobalMesh"), GlobalMeshJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JsonObject;
|
||||
}
|
||||
|
||||
void AMRUKAnchor::EndPlay(EEndPlayReason::Type Reason)
|
||||
{
|
||||
if (Interior)
|
||||
{
|
||||
Interior->Destroy();
|
||||
}
|
||||
Super::EndPlay(Reason);
|
||||
}
|
||||
|
||||
bool AMRUKAnchor::RayCastPlane(const FRay& LocalRay, float MaxDist, FMRUKHit& OutHit)
|
||||
{
|
||||
// If the ray is behind or parallel to the anchor's plane then ignore it
|
||||
if (LocalRay.Direction.X >= UE_KINDA_SMALL_NUMBER)
|
||||
{
|
||||
// Distance to the plane from the ray origin along the ray's direction
|
||||
const float Dist = -LocalRay.Origin.X / LocalRay.Direction.X;
|
||||
// If the distance is negative or less than the maximum distance then ignore it
|
||||
if (Dist >= 0.0f && (MaxDist <= 0 || Dist < MaxDist))
|
||||
{
|
||||
const FVector HitPos = LocalRay.PointAt(Dist);
|
||||
// Ensure the hit is within the plane extends and within the boundary
|
||||
const FVector2D Pos2D(HitPos.Y, HitPos.Z);
|
||||
if (PlaneBounds.IsInside(Pos2D) && IsPositionInBoundary(Pos2D))
|
||||
{
|
||||
// Transform the result back into world space
|
||||
const auto Transform = GetTransform();
|
||||
OutHit.HitPosition = Transform.TransformPositionNoScale(HitPos);
|
||||
OutHit.HitNormal = Transform.TransformVectorNoScale(-FVector::XAxisVector);
|
||||
OutHit.HitDistance = Dist;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AMRUKAnchor::RayCastVolume(const FRay& LocalRay, float MaxDist, FMRUKHit& OutHit)
|
||||
{
|
||||
// Use the slab method to determine if the ray intersects with the bounding box
|
||||
// https://education.siggraph.org/static/HyperGraph/raytrace/rtinter3.htm
|
||||
float DistNear = -UE_BIG_NUMBER, DistFar = UE_BIG_NUMBER;
|
||||
int HitAxis = 0;
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
if (FMath::Abs(LocalRay.Direction.Component(i)) >= UE_KINDA_SMALL_NUMBER)
|
||||
{
|
||||
// Distance to the plane from the ray origin along the ray's direction
|
||||
float Dist1 = (VolumeBounds.Min.Component(i) - LocalRay.Origin.Component(i)) / LocalRay.Direction.Component(i);
|
||||
float Dist2 = (VolumeBounds.Max.Component(i) - LocalRay.Origin.Component(i)) / LocalRay.Direction.Component(i);
|
||||
|
||||
if (Dist1 > Dist2)
|
||||
{
|
||||
std::swap(Dist1, Dist2);
|
||||
}
|
||||
if (Dist1 > DistNear)
|
||||
{
|
||||
DistNear = Dist1;
|
||||
HitAxis = i;
|
||||
}
|
||||
if (Dist2 < DistFar)
|
||||
{
|
||||
DistFar = Dist2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// In this case there is no intersection because the ray is parallel to the plane
|
||||
// Check that it is within bounds
|
||||
if (LocalRay.Origin.Component(i) < VolumeBounds.Min.Component(i) || LocalRay.Origin.Component(i) > VolumeBounds.Max.Component(i))
|
||||
{
|
||||
// No intersection, set DistNear to a large number
|
||||
DistNear = UE_BIG_NUMBER;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (DistNear >= 0 && DistNear <= DistFar && (MaxDist <= 0 || DistNear < MaxDist))
|
||||
{
|
||||
const FVector HitPos = LocalRay.PointAt(DistNear);
|
||||
FVector HitNormal = FVector::ZeroVector;
|
||||
HitNormal.Component(HitAxis) = LocalRay.Direction.Component(HitAxis) > 0 ? -1 : 1;
|
||||
// Transform the result back into world space
|
||||
const auto Transform = GetTransform();
|
||||
OutHit.HitPosition = Transform.TransformPositionNoScale(HitPos);
|
||||
OutHit.HitNormal = Transform.TransformVectorNoScale(HitNormal);
|
||||
OutHit.HitDistance = DistNear;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AMRUKAnchor::TriangulatedMeshCache::Clear()
|
||||
{
|
||||
Vertices.Empty();
|
||||
Triangles.Empty();
|
||||
Areas.Empty();
|
||||
TotalArea = 0.0f;
|
||||
}
|
||||
|
||||
// #pragma optimize("", on)
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,738 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitAnchorActorSpawner.h"
|
||||
|
||||
#include "MRUtilityKitAnchor.h"
|
||||
#include "MRUtilityKitTelemetry.h"
|
||||
#include "MRUtilityKitSubsystem.h"
|
||||
#include "MRUtilityKitBPLibrary.h"
|
||||
#include "GameFramework/WorldSettings.h"
|
||||
|
||||
#include "Engine/GameInstance.h"
|
||||
|
||||
const FName GMRUK_PROCEDURAL_ANCHOR_MESH_TAG = TEXT("MRUKProceduralAnchorMesh");
|
||||
|
||||
namespace
|
||||
{
|
||||
AActor* SpawnProceduralMesh(AMRUKAnchor* Anchor, const TArray<FMRUKPlaneUV>& PlaneUVAdjustments, const TArray<FString>& CutHoleLabels, UMaterialInterface* Material)
|
||||
{
|
||||
AActor* Actor = Anchor->GetWorld()->SpawnActor<AActor>();
|
||||
Actor->SetOwner(Anchor);
|
||||
Actor->Tags.AddUnique(GMRUK_PROCEDURAL_ANCHOR_MESH_TAG);
|
||||
Actor->SetRootComponent(NewObject<USceneComponent>(Actor, TEXT("Root")));
|
||||
Actor->GetRootComponent()->SetMobility(EComponentMobility::Movable);
|
||||
Actor->AttachToComponent(Anchor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
|
||||
Actor->SetActorRelativeScale3D(FVector::OneVector);
|
||||
|
||||
UProceduralMeshComponent* ProceduralMeshComponent = NewObject<UProceduralMeshComponent>(Actor, TEXT("ProceduralMesh"));
|
||||
ProceduralMeshComponent->SetupAttachment(Actor->GetRootComponent());
|
||||
ProceduralMeshComponent->RegisterComponent();
|
||||
Actor->AddInstanceComponent(ProceduralMeshComponent);
|
||||
|
||||
Anchor->GenerateProceduralAnchorMesh(ProceduralMeshComponent, PlaneUVAdjustments, CutHoleLabels, false, true);
|
||||
|
||||
for (int32 SectionIndex = 0; SectionIndex < ProceduralMeshComponent->GetNumSections(); ++SectionIndex)
|
||||
{
|
||||
ProceduralMeshComponent->SetMaterial(SectionIndex, Material);
|
||||
}
|
||||
return Actor;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void AMRUKAnchorActorSpawner::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadAnchorActorSpawnerMarker> Event(static_cast<int>(GetTypeHash(this)));
|
||||
|
||||
if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly)
|
||||
{
|
||||
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
if (Subsystem->SceneLoadStatus == EMRUKInitStatus::Complete)
|
||||
{
|
||||
SpawnActors(Subsystem->GetCurrentRoom());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only listen for the room created event in case no current room was available yet
|
||||
Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKAnchorActorSpawner::OnRoomCreated);
|
||||
}
|
||||
}
|
||||
else if (SpawnMode == EMRUKSpawnMode::AllRooms)
|
||||
{
|
||||
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
for (auto Room : Subsystem->Rooms)
|
||||
{
|
||||
SpawnActors(Room);
|
||||
}
|
||||
|
||||
// Listen for new rooms that get created
|
||||
Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKAnchorActorSpawner::OnRoomCreated);
|
||||
}
|
||||
}
|
||||
|
||||
void AMRUKAnchorActorSpawner::OnRoomCreated(AMRUKRoom* Room)
|
||||
{
|
||||
if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly && GetGameInstance()->GetSubsystem<UMRUKSubsystem>()->GetCurrentRoom() != Room)
|
||||
{
|
||||
// Skip this room if it is not the current room
|
||||
return;
|
||||
}
|
||||
SpawnActors(Room);
|
||||
}
|
||||
|
||||
void AMRUKAnchorActorSpawner::OnRoomUpdated(AMRUKRoom* Room)
|
||||
{
|
||||
if (!SpawnedActors.Find(Room))
|
||||
{
|
||||
// A room was updated that we don't care about. If we are in current room only mode
|
||||
// we only want to update the one room we created
|
||||
return;
|
||||
}
|
||||
SpawnActors(Room);
|
||||
}
|
||||
|
||||
void AMRUKAnchorActorSpawner::OnRoomRemoved(AMRUKRoom* Room)
|
||||
{
|
||||
RemoveActors(Room);
|
||||
}
|
||||
|
||||
void AMRUKAnchorActorSpawner::RemoveActors(AMRUKRoom* Room)
|
||||
{
|
||||
if (!IsValid(Room))
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Can not remove actors from room that is a nullptr"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (TArray<AActor*>* Actors = SpawnedActors.Find(Room))
|
||||
{
|
||||
for (AActor* Actor : *Actors)
|
||||
{
|
||||
if (IsValid(Actor))
|
||||
{
|
||||
Actor->Destroy();
|
||||
}
|
||||
}
|
||||
Actors->Empty();
|
||||
SpawnedActors.Remove(Room);
|
||||
}
|
||||
}
|
||||
|
||||
bool AMRUKAnchorActorSpawner::ShouldAnchorFallbackToProceduralMesh(const FMRUKSpawnGroup& SpawnGroup) const
|
||||
{
|
||||
switch (SpawnGroup.FallbackToProcedural)
|
||||
{
|
||||
case EMRUKFallbackToProceduralOverwrite::Default:
|
||||
return ShouldFallbackToProcedural;
|
||||
case EMRUKFallbackToProceduralOverwrite::Fallback:
|
||||
return true;
|
||||
case EMRUKFallbackToProceduralOverwrite::NoFallback:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
TArray<AActor*> AMRUKAnchorActorSpawner::SpawnProceduralMeshesOnWallsIfNoWallActorGiven(AMRUKRoom* Room)
|
||||
{
|
||||
TArray<AActor*> Actors;
|
||||
const auto WallFace = SpawnGroups.Find(FMRUKLabels::WallFace);
|
||||
if (!WallFace || (WallFace->Actors.IsEmpty() && ShouldAnchorFallbackToProceduralMesh(*WallFace)))
|
||||
{
|
||||
// If no wall mesh is given we want to spawn the walls procedural to make seamless UVs
|
||||
TArray<FMRUKAnchorWithPlaneUVs> AnchorsWithPlaneUVs;
|
||||
Room->ComputeWallMeshUVAdjustments({}, AnchorsWithPlaneUVs);
|
||||
for (const auto& AnchorWithPlaneUVs : AnchorsWithPlaneUVs)
|
||||
{
|
||||
Actors.Push(SpawnProceduralMesh(AnchorWithPlaneUVs.Anchor, AnchorWithPlaneUVs.PlaneUVs, CutHoleLabels, ProceduralMaterial));
|
||||
}
|
||||
}
|
||||
return Actors;
|
||||
}
|
||||
|
||||
AActor* AMRUKAnchorActorSpawner::SpawnProceduralMeshOnFloorIfNoFloorActorGiven(AMRUKRoom* Room)
|
||||
{
|
||||
const auto Floor = SpawnGroups.Find(FMRUKLabels::Floor);
|
||||
if (Room->FloorAnchor && (!Floor || (Floor->Actors.IsEmpty() && ShouldAnchorFallbackToProceduralMesh(*Floor))))
|
||||
{
|
||||
// Use metric scaling to match walls
|
||||
const float WorldToMeters = GetWorldSettings()->WorldToMeters;
|
||||
const FVector2D Scale = Room->FloorAnchor->PlaneBounds.GetSize() / WorldToMeters;
|
||||
const TArray<FMRUKPlaneUV> PlaneUVAdj = { { FVector2D::ZeroVector, Scale } };
|
||||
return SpawnProceduralMesh(Room->FloorAnchor, PlaneUVAdj, CutHoleLabels, ProceduralMaterial);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AActor* AMRUKAnchorActorSpawner::SpawnProceduralMeshOnCeilingIfNoCeilingActorGiven(AMRUKRoom* Room)
|
||||
{
|
||||
const auto Ceiling = SpawnGroups.Find(FMRUKLabels::Ceiling);
|
||||
if (Room->CeilingAnchor && (!Ceiling || (Ceiling->Actors.IsEmpty() && ShouldAnchorFallbackToProceduralMesh(*Ceiling))))
|
||||
{
|
||||
// Use metric scaling to match walls
|
||||
const float WorldToMeters = GetWorldSettings()->WorldToMeters;
|
||||
const FVector2D Scale = Room->CeilingAnchor->PlaneBounds.GetSize() / WorldToMeters;
|
||||
const TArray<FMRUKPlaneUV> PlaneUVAdj = { { FVector2D::ZeroVector, Scale } };
|
||||
return SpawnProceduralMesh(Room->CeilingAnchor, PlaneUVAdj, CutHoleLabels, ProceduralMaterial);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AActor* AMRUKAnchorActorSpawner::SpawnProceduralMeshForAnchorIfNeeded(AMRUKAnchor* Anchor)
|
||||
{
|
||||
if (!IsValid(Anchor))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (Anchor->SemanticClassifications.IsEmpty())
|
||||
{
|
||||
// For unknown scene objects spawn a procedural mesh (should not happen in practice)
|
||||
return SpawnProceduralMesh(Anchor, {}, CutHoleLabels, ProceduralMaterial);
|
||||
}
|
||||
|
||||
for (const FString& Label : Anchor->SemanticClassifications)
|
||||
{
|
||||
if (Label == FMRUKLabels::WallFace && Anchor->SemanticClassifications.Contains(FMRUKLabels::InvisibleWallFace))
|
||||
{
|
||||
// Treat anchors with WALL_FACE and INVISIBLE_WALL_FACE as anchors that only have INVISIBLE_WALL_FACE
|
||||
continue;
|
||||
}
|
||||
|
||||
const FMRUKSpawnGroup* SpawnGroup = SpawnGroups.Find(Label);
|
||||
if (SpawnGroup && SpawnGroup->Actors.IsEmpty() && ShouldAnchorFallbackToProceduralMesh(*SpawnGroup))
|
||||
{
|
||||
return SpawnProceduralMesh(Anchor, {}, CutHoleLabels, ProceduralMaterial);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TArray<AActor*> AMRUKAnchorActorSpawner::SpawnProceduralMeshesInRoom(AMRUKRoom* Room)
|
||||
{
|
||||
TArray<AActor*> Actors;
|
||||
|
||||
const TArray<AActor*> WallActors = SpawnProceduralMeshesOnWallsIfNoWallActorGiven(Room);
|
||||
if (!WallActors.IsEmpty())
|
||||
{
|
||||
Actors.Append(WallActors);
|
||||
}
|
||||
|
||||
AActor* Actor = nullptr;
|
||||
|
||||
Actor = SpawnProceduralMeshOnFloorIfNoFloorActorGiven(Room);
|
||||
if (Actor)
|
||||
{
|
||||
Actors.Push(Actor);
|
||||
}
|
||||
Actor = SpawnProceduralMeshOnCeilingIfNoCeilingActorGiven(Room);
|
||||
if (Actor)
|
||||
{
|
||||
Actors.Push(Actor);
|
||||
}
|
||||
for (const auto& Anchor : Room->AllAnchors)
|
||||
{
|
||||
if (Anchor->HasLabel(FMRUKLabels::Floor) || Anchor->HasLabel(FMRUKLabels::Ceiling) || Anchor->HasLabel(FMRUKLabels::WallFace))
|
||||
{
|
||||
// These have already been spawned above in case it was necessary
|
||||
continue;
|
||||
}
|
||||
Actor = SpawnProceduralMeshForAnchorIfNeeded(Anchor);
|
||||
if (Actor)
|
||||
{
|
||||
Actors.Push(Actor);
|
||||
}
|
||||
}
|
||||
|
||||
return Actors;
|
||||
}
|
||||
|
||||
bool AMRUKAnchorActorSpawner::SelectSpawnActorClosestSize(AMRUKAnchor* Anchor, const FMRUKSpawnGroup& SpawnGroup, FMRUKSpawnActor& OutSpawnActor)
|
||||
{
|
||||
if (SpawnGroup.Actors.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int Index = 0;
|
||||
if (SpawnGroup.Actors.Num() > 1)
|
||||
{
|
||||
if (Anchor->VolumeBounds.IsValid)
|
||||
{
|
||||
const double AnchorSize = FMath::Pow(Anchor->VolumeBounds.GetVolume(), 1.0 / 3.0);
|
||||
double ClosestSizeDifference = UE_BIG_NUMBER;
|
||||
for (int i = 0; i < SpawnGroup.Actors.Num(); ++i)
|
||||
{
|
||||
const auto& SpawnActor = SpawnGroup.Actors[i];
|
||||
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
auto Bounds = Subsystem->GetActorClassBounds(SpawnActor.Actor);
|
||||
if (Bounds.IsValid)
|
||||
{
|
||||
const double SpawnActorSize = FMath::Pow(Bounds.GetVolume(), 1.0 / 3.0);
|
||||
const double SizeDifference = FMath::Abs(AnchorSize - SpawnActorSize);
|
||||
if (SizeDifference < ClosestSizeDifference)
|
||||
{
|
||||
ClosestSizeDifference = SizeDifference;
|
||||
Index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
OutSpawnActor = SpawnGroup.Actors[Index];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AMRUKAnchorActorSpawner::SelectSpawnActorRandom(const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream, FMRUKSpawnActor& OutSpawnActor)
|
||||
{
|
||||
if (SpawnGroup.Actors.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const int Index = RandomStream.RandRange(0, SpawnGroup.Actors.Num() - 1);
|
||||
OutSpawnActor = SpawnGroup.Actors[Index];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AMRUKAnchorActorSpawner::SelectSpawnActorFromSpawnGroup(AMRUKAnchor* Anchor, const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream, FMRUKSpawnActor& OutSpawnActor)
|
||||
{
|
||||
if (SpawnGroup.Actors.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (SpawnGroup.SelectionMode == EMRUKSpawnerSelectionMode::Random)
|
||||
{
|
||||
return SelectSpawnActorRandom(SpawnGroup, RandomStream, OutSpawnActor);
|
||||
}
|
||||
if (SpawnGroup.SelectionMode == EMRUKSpawnerSelectionMode::ClosestSize)
|
||||
{
|
||||
return SelectSpawnActorClosestSize(Anchor, SpawnGroup, OutSpawnActor);
|
||||
}
|
||||
if (SpawnGroup.SelectionMode == EMRUKSpawnerSelectionMode::Custom)
|
||||
{
|
||||
return SelectSpawnActorCustom(Anchor, SpawnGroup, RandomStream, OutSpawnActor);
|
||||
}
|
||||
OutSpawnActor = SpawnGroup.Actors[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
void AMRUKAnchorActorSpawner::AttachAndFitActorToAnchor(AMRUKAnchor* Anchor, AActor* Actor, EMRUKSpawnerScalingMode ScalingMode, EMRUKAlignMode AlignMode, bool bCalculateFacingDirection, bool bMatchAspectRatio)
|
||||
{
|
||||
auto ActorRoot = Actor->GetRootComponent();
|
||||
if (!ActorRoot)
|
||||
{
|
||||
UE_LOG(LogMRUK, Error, TEXT("Spawned actor does not have a root component."));
|
||||
return;
|
||||
}
|
||||
ActorRoot->SetMobility(EComponentMobility::Movable);
|
||||
Actor->AttachToComponent(Anchor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
|
||||
Actor->SetActorRelativeScale3D(FVector::OneVector);
|
||||
|
||||
const auto ChildLocalBounds = Actor->CalculateComponentsBoundingBoxInLocalSpace(true);
|
||||
FQuat Rotation = FQuat::Identity;
|
||||
FVector Offset = FVector::ZeroVector;
|
||||
FVector Scale = FVector::OneVector;
|
||||
|
||||
if (Anchor->VolumeBounds.IsValid)
|
||||
{
|
||||
int CardinalAxisIndex = 0;
|
||||
if (bCalculateFacingDirection && !bMatchAspectRatio)
|
||||
{
|
||||
// Pick rotation that is pointing away from the closest wall
|
||||
// If we are also matching the aspect ratio then we only have a choice
|
||||
// between 2 directions and first need to figure out what those 2 directions
|
||||
// are before doing the ray casting.
|
||||
UMRUKBPLibrary::ComputeDirectionAwayFromClosestWall(Anchor, CardinalAxisIndex, {});
|
||||
}
|
||||
Rotation = FQuat::MakeFromEuler(FVector(90, -(CardinalAxisIndex + 1) * 90, 90));
|
||||
|
||||
FBox ChildBounds = ChildLocalBounds.TransformBy(FTransform(Rotation));
|
||||
const FVector ChildSize1 = ChildBounds.GetSize();
|
||||
|
||||
Scale = Anchor->VolumeBounds.GetSize() / ChildSize1;
|
||||
|
||||
if (bMatchAspectRatio)
|
||||
{
|
||||
FVector ChildSize2 = ChildSize1;
|
||||
Swap(ChildSize2.Y, ChildSize2.Z);
|
||||
FVector Scale2 = Anchor->VolumeBounds.GetSize() / ChildSize2;
|
||||
|
||||
float Distortion1 = FMath::Max(Scale.Y, Scale.Z) / FMath::Min(Scale.Y, Scale.Z);
|
||||
float Distortion2 = FMath::Max(Scale2.Y, Scale2.Z) / FMath::Min(Scale2.Y, Scale2.Z);
|
||||
|
||||
bool FlipToMatchAspectRatio = Distortion1 > Distortion2;
|
||||
if (FlipToMatchAspectRatio)
|
||||
{
|
||||
CardinalAxisIndex = 1;
|
||||
Scale = Scale2;
|
||||
}
|
||||
if (bCalculateFacingDirection)
|
||||
{
|
||||
UMRUKBPLibrary::ComputeDirectionAwayFromClosestWall(Anchor, CardinalAxisIndex, FlipToMatchAspectRatio ? TArray<int>{ 0, 2 } : TArray<int>{ 1, 3 });
|
||||
}
|
||||
if (CardinalAxisIndex != 0)
|
||||
{
|
||||
// Update the rotation and child bounds if necessary
|
||||
Rotation = FQuat::MakeFromEuler(FVector(90, -(CardinalAxisIndex + 1) * 90, 90));
|
||||
ChildBounds = ChildLocalBounds.TransformBy(FTransform(Rotation));
|
||||
}
|
||||
}
|
||||
|
||||
switch (ScalingMode)
|
||||
{
|
||||
case EMRUKSpawnerScalingMode::UniformScaling:
|
||||
Scale.X = Scale.Y = Scale.Z = FMath::Min3(Scale.X, Scale.Y, Scale.Z);
|
||||
break;
|
||||
case EMRUKSpawnerScalingMode::UniformXYScale:
|
||||
Scale.Y = Scale.Z = FMath::Min(Scale.Y, Scale.Z);
|
||||
break;
|
||||
case EMRUKSpawnerScalingMode::NoScaling:
|
||||
Scale = FVector::OneVector;
|
||||
break;
|
||||
case EMRUKSpawnerScalingMode::Stretch:
|
||||
// Nothing to do
|
||||
break;
|
||||
case EMRUKSpawnerScalingMode::Custom:
|
||||
Scale = ComputeCustomScaling(Anchor, Actor, Scale);
|
||||
break;
|
||||
}
|
||||
|
||||
if (AlignMode == EMRUKAlignMode::Custom)
|
||||
{
|
||||
Offset = ComputeCustomAlign(Anchor, Actor, ChildBounds, Scale);
|
||||
}
|
||||
else if (AlignMode != EMRUKAlignMode::None)
|
||||
{
|
||||
FVector ChildBase;
|
||||
FVector VolumeBase;
|
||||
|
||||
switch (AlignMode)
|
||||
{
|
||||
case EMRUKAlignMode::CenterOnCenter:
|
||||
ChildBase = FVector(0.5 * (ChildBounds.Min.X + ChildBounds.Max.X), 0.5 * (ChildBounds.Min.Y + ChildBounds.Max.Y), 0.5 * (ChildBounds.Min.Z + ChildBounds.Max.Z));
|
||||
break;
|
||||
|
||||
case EMRUKAlignMode::TopOnTop:
|
||||
case EMRUKAlignMode::TopOnBottom:
|
||||
ChildBase = FVector(ChildBounds.Min.X, 0.5 * (ChildBounds.Min.Y + ChildBounds.Max.Y), 0.5 * (ChildBounds.Min.Z + ChildBounds.Max.Z));
|
||||
break;
|
||||
|
||||
case EMRUKAlignMode::Default:
|
||||
case EMRUKAlignMode::BottomOnBottom:
|
||||
case EMRUKAlignMode::BottomOnTop:
|
||||
ChildBase = FVector(ChildBounds.Max.X, 0.5 * (ChildBounds.Min.Y + ChildBounds.Max.Y), 0.5 * (ChildBounds.Min.Z + ChildBounds.Max.Z));
|
||||
break;
|
||||
|
||||
case EMRUKAlignMode::LeftOnLeft:
|
||||
case EMRUKAlignMode::LeftOnRight:
|
||||
ChildBase = FVector(0.5 * (ChildBounds.Min.X + ChildBounds.Max.X), 0.5 * (ChildBounds.Min.Y + ChildBounds.Max.Y), ChildBounds.Max.Z);
|
||||
break;
|
||||
|
||||
case EMRUKAlignMode::RightOnRight:
|
||||
case EMRUKAlignMode::RightOnLeft:
|
||||
ChildBase = FVector(0.5 * (ChildBounds.Min.X + ChildBounds.Max.X), 0.5 * (ChildBounds.Min.Y + ChildBounds.Max.Y), ChildBounds.Min.Z);
|
||||
break;
|
||||
|
||||
case EMRUKAlignMode::FrontOnFront:
|
||||
case EMRUKAlignMode::FrontOnBack:
|
||||
ChildBase = FVector(0.5 * (ChildBounds.Min.X + ChildBounds.Max.X), ChildBounds.Max.Y, 0.5 * (ChildBounds.Min.Z + ChildBounds.Max.Z));
|
||||
break;
|
||||
|
||||
case EMRUKAlignMode::BackOnBack:
|
||||
case EMRUKAlignMode::BackOnFront:
|
||||
ChildBase = FVector(0.5 * (ChildBounds.Min.X + ChildBounds.Max.X), ChildBounds.Min.Y, 0.5 * (ChildBounds.Min.Z + ChildBounds.Max.Z));
|
||||
break;
|
||||
}
|
||||
|
||||
switch (AlignMode)
|
||||
{
|
||||
case EMRUKAlignMode::CenterOnCenter:
|
||||
VolumeBase = FVector(0.5 * (Anchor->VolumeBounds.Min.X + Anchor->VolumeBounds.Max.X), 0.5 * (Anchor->VolumeBounds.Min.Y + Anchor->VolumeBounds.Max.Y), 0.5 * (Anchor->VolumeBounds.Min.Z + Anchor->VolumeBounds.Max.Z));
|
||||
break;
|
||||
|
||||
case EMRUKAlignMode::TopOnTop:
|
||||
case EMRUKAlignMode::BottomOnTop:
|
||||
VolumeBase = FVector(Anchor->VolumeBounds.Min.X, 0.5 * (Anchor->VolumeBounds.Min.Y + Anchor->VolumeBounds.Max.Y), 0.5 * (Anchor->VolumeBounds.Min.Z + Anchor->VolumeBounds.Max.Z));
|
||||
break;
|
||||
|
||||
case EMRUKAlignMode::Default:
|
||||
case EMRUKAlignMode::BottomOnBottom:
|
||||
case EMRUKAlignMode::TopOnBottom:
|
||||
VolumeBase = FVector(Anchor->VolumeBounds.Max.X, 0.5 * (Anchor->VolumeBounds.Min.Y + Anchor->VolumeBounds.Max.Y), 0.5 * (Anchor->VolumeBounds.Min.Z + Anchor->VolumeBounds.Max.Z));
|
||||
break;
|
||||
|
||||
case EMRUKAlignMode::LeftOnLeft:
|
||||
case EMRUKAlignMode::RightOnLeft:
|
||||
VolumeBase = FVector(0.5 * (Anchor->VolumeBounds.Min.X + Anchor->VolumeBounds.Max.X), 0.5 * (Anchor->VolumeBounds.Min.Y + Anchor->VolumeBounds.Max.Y), Anchor->VolumeBounds.Max.Z);
|
||||
break;
|
||||
|
||||
case EMRUKAlignMode::RightOnRight:
|
||||
case EMRUKAlignMode::LeftOnRight:
|
||||
VolumeBase = FVector(0.5 * (Anchor->VolumeBounds.Min.X + Anchor->VolumeBounds.Max.X), 0.5 * (Anchor->VolumeBounds.Min.Y + Anchor->VolumeBounds.Max.Y), Anchor->VolumeBounds.Min.Z);
|
||||
break;
|
||||
|
||||
case EMRUKAlignMode::FrontOnFront:
|
||||
case EMRUKAlignMode::BackOnFront:
|
||||
VolumeBase = FVector(0.5 * (Anchor->VolumeBounds.Min.X + Anchor->VolumeBounds.Max.X), Anchor->VolumeBounds.Max.Y, 0.5 * (Anchor->VolumeBounds.Min.Z + Anchor->VolumeBounds.Max.Z));
|
||||
break;
|
||||
|
||||
case EMRUKAlignMode::BackOnBack:
|
||||
case EMRUKAlignMode::FrontOnBack:
|
||||
VolumeBase = FVector(0.5 * (Anchor->VolumeBounds.Min.X + Anchor->VolumeBounds.Max.X), Anchor->VolumeBounds.Min.Y, 0.5 * (Anchor->VolumeBounds.Min.Z + Anchor->VolumeBounds.Max.Z));
|
||||
break;
|
||||
}
|
||||
Offset = VolumeBase - ChildBase * Scale;
|
||||
}
|
||||
}
|
||||
else if (Anchor->PlaneBounds.bIsValid)
|
||||
{
|
||||
const auto XAxis = Anchor->GetTransform().GetUnitAxis(EAxis::X);
|
||||
// Adjust the rotation so that Z always points up. This enables assets to be authored in a more natural
|
||||
// way and show up in the scene as expected.
|
||||
if (XAxis.Z <= -UE_INV_SQRT_2)
|
||||
{
|
||||
// This is a floor or other surface facing upwards
|
||||
Rotation = FQuat::MakeFromEuler(FVector(0, 90, 0));
|
||||
}
|
||||
else if (XAxis.Z >= UE_INV_SQRT_2)
|
||||
{
|
||||
// This is ceiling or other surface facing downwards.
|
||||
Rotation = FQuat::MakeFromEuler(FVector(0, -90, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a wall or other upright surface.
|
||||
Rotation = FQuat::MakeFromEuler(FVector(0, 0, 180));
|
||||
}
|
||||
|
||||
const auto ChildBounds = ChildLocalBounds.TransformBy(FTransform(Rotation));
|
||||
const auto ChildBounds2D = FBox2D(FVector2D(ChildBounds.Min.Y, ChildBounds.Min.Z), FVector2D(ChildBounds.Max.Y, ChildBounds.Max.Z));
|
||||
auto Scale2D = Anchor->PlaneBounds.GetSize() / ChildBounds2D.GetSize();
|
||||
|
||||
switch (ScalingMode)
|
||||
{
|
||||
case EMRUKSpawnerScalingMode::UniformScaling:
|
||||
case EMRUKSpawnerScalingMode::UniformXYScale:
|
||||
Scale2D.X = Scale2D.Y = FMath::Min(Scale2D.X, Scale2D.Y);
|
||||
break;
|
||||
case EMRUKSpawnerScalingMode::NoScaling:
|
||||
Scale2D = FVector2D::UnitVector;
|
||||
break;
|
||||
case EMRUKSpawnerScalingMode::Stretch:
|
||||
// Nothing to do
|
||||
break;
|
||||
case EMRUKSpawnerScalingMode::Custom:
|
||||
const FVector S = ComputeCustomScaling(Anchor, Actor, FVector(Scale2D.X, Scale2D.Y, 0.0));
|
||||
Scale2D.X = S.X;
|
||||
Scale2D.Y = S.Y;
|
||||
break;
|
||||
}
|
||||
|
||||
FVector2D Offset2D = FVector2D::ZeroVector;
|
||||
switch (AlignMode)
|
||||
{
|
||||
case EMRUKAlignMode::None:
|
||||
case EMRUKAlignMode::BackOnBack:
|
||||
case EMRUKAlignMode::FrontOnFront:
|
||||
case EMRUKAlignMode::FrontOnBack:
|
||||
case EMRUKAlignMode::BackOnFront:
|
||||
Offset = FVector::ZeroVector;
|
||||
break;
|
||||
case EMRUKAlignMode::Default:
|
||||
case EMRUKAlignMode::CenterOnCenter:
|
||||
Offset2D = Anchor->PlaneBounds.GetCenter() - ChildBounds2D.GetCenter() * Scale2D;
|
||||
break;
|
||||
case EMRUKAlignMode::BottomOnBottom:
|
||||
Offset2D = FVector2D(Anchor->PlaneBounds.GetCenter().X, Anchor->PlaneBounds.Min.Y) - FVector2D(ChildBounds2D.GetCenter().X, ChildBounds2D.Min.Y) * Scale2D;
|
||||
break;
|
||||
case EMRUKAlignMode::TopOnTop:
|
||||
Offset2D = FVector2D(Anchor->PlaneBounds.GetCenter().X, Anchor->PlaneBounds.Max.Y) - FVector2D(ChildBounds2D.GetCenter().X, ChildBounds2D.Max.Y) * Scale2D;
|
||||
break;
|
||||
case EMRUKAlignMode::LeftOnLeft:
|
||||
Offset2D = FVector2D(Anchor->PlaneBounds.Max.X, Anchor->PlaneBounds.GetCenter().Y) - FVector2D(ChildBounds2D.Max.X, ChildBounds2D.GetCenter().Y) * Scale2D;
|
||||
break;
|
||||
case EMRUKAlignMode::RightOnRight:
|
||||
Offset2D = FVector2D(Anchor->PlaneBounds.Min.X, Anchor->PlaneBounds.GetCenter().Y) - FVector2D(ChildBounds2D.Min.X, ChildBounds2D.GetCenter().Y) * Scale2D;
|
||||
break;
|
||||
case EMRUKAlignMode::BottomOnTop:
|
||||
Offset2D = FVector2D(Anchor->PlaneBounds.GetCenter().X, Anchor->PlaneBounds.Max.Y) - FVector2D(ChildBounds2D.GetCenter().X, ChildBounds2D.Min.Y) * Scale2D;
|
||||
break;
|
||||
case EMRUKAlignMode::TopOnBottom:
|
||||
Offset2D = FVector2D(Anchor->PlaneBounds.GetCenter().X, Anchor->PlaneBounds.Min.Y) - FVector2D(ChildBounds2D.GetCenter().X, ChildBounds2D.Max.Y) * Scale2D;
|
||||
break;
|
||||
case EMRUKAlignMode::LeftOnRight:
|
||||
Offset2D = FVector2D(Anchor->PlaneBounds.Min.X, Anchor->PlaneBounds.GetCenter().Y) - FVector2D(ChildBounds2D.Max.X, ChildBounds2D.GetCenter().Y) * Scale2D;
|
||||
break;
|
||||
case EMRUKAlignMode::RightOnLeft:
|
||||
Offset2D = FVector2D(Anchor->PlaneBounds.Max.X, Anchor->PlaneBounds.GetCenter().Y) - FVector2D(ChildBounds2D.Min.X, ChildBounds2D.GetCenter().Y) * Scale2D;
|
||||
break;
|
||||
case EMRUKAlignMode::Custom:
|
||||
Offset = ComputeCustomAlign(Anchor, Actor, FBox(FVector(ChildBounds2D.Min, 0.0), FVector(ChildBounds2D.Max, 0.0)), FVector(Scale2D.X, Scale2D.Y, 0.0));
|
||||
Offset2D = FVector2D(Offset.X, Offset.Y);
|
||||
break;
|
||||
}
|
||||
|
||||
Offset = FVector(0.0, Offset2D.X, Offset2D.Y);
|
||||
Scale = FVector(0.5 * (Scale2D.X + Scale2D.Y), Scale2D.X, Scale2D.Y);
|
||||
}
|
||||
Actor->SetActorRelativeRotation(Rotation);
|
||||
Actor->SetActorRelativeLocation(Offset);
|
||||
UMRUKBPLibrary::SetScaleRecursivelyAdjustingForRotation(ActorRoot, Scale);
|
||||
}
|
||||
|
||||
AActor* AMRUKAnchorActorSpawner::SpawnAnchorActor_Implementation(AMRUKAnchor* Anchor, const FMRUKSpawnActor& SpawnActor)
|
||||
{
|
||||
AActor* SpawnedActor = GetWorld()->SpawnActor(SpawnActor.Actor);
|
||||
AttachAndFitActorToAnchor(Anchor, SpawnedActor, SpawnActor.ScalingMode, SpawnActor.AlignMode, SpawnActor.CalculateFacingDirection, SpawnActor.MatchAspectRatio);
|
||||
return SpawnedActor;
|
||||
}
|
||||
|
||||
FVector AMRUKAnchorActorSpawner::ComputeCustomScaling_Implementation(AMRUKAnchor* Anchor, AActor* SpawnedActor, const FVector& StretchedScale)
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Custom scaling mode selected but default implementation used. Please override ComputeCustomScaling() to define custom scaling"));
|
||||
return StretchedScale;
|
||||
}
|
||||
|
||||
bool AMRUKAnchorActorSpawner::SelectSpawnActorCustom_Implementation(AMRUKAnchor* Anchor, const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream, FMRUKSpawnActor& OutSpawnActor)
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Custom selection mode specified, but custom selection logic was not overwritten. Please overwrite SelectSpawnActorCustom() to define custom selection logic"));
|
||||
return SelectSpawnActorRandom(SpawnGroup, RandomStream, OutSpawnActor);
|
||||
}
|
||||
|
||||
FVector AMRUKAnchorActorSpawner::ComputeCustomAlign_Implementation(AMRUKAnchor* Anchor, AActor* Actor, const FBox& ChildBounds, const FVector& Scale)
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Custom align mode selected but default implementation used. Please override ComputeCustomAlign() to define custom align"));
|
||||
return FVector::ZeroVector;
|
||||
}
|
||||
|
||||
bool AMRUKAnchorActorSpawner::ShouldSpawnActorForAnchor(AMRUKAnchor* Anchor, const FString& Label, FMRUKSpawnGroup& OutSpawnGroup) const
|
||||
{
|
||||
if (Label == FMRUKLabels::WallFace && Anchor->SemanticClassifications.Contains(FMRUKLabels::InvisibleWallFace))
|
||||
{
|
||||
// Treat anchors with WALL_FACE and INVISIBLE_WALL_FACE as anchors that only have INVISIBLE_WALL_FACE
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto SpawnGroup = SpawnGroups.Find(Label);
|
||||
if (!SpawnGroup)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (SpawnGroup->Actors.IsEmpty() && ShouldAnchorFallbackToProceduralMesh(*SpawnGroup))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutSpawnGroup = *SpawnGroup;
|
||||
return true;
|
||||
}
|
||||
|
||||
AActor* AMRUKAnchorActorSpawner::SpawnAnchorActorForLabel_Implementation(AMRUKAnchor* Anchor, const FString& Label, const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream)
|
||||
{
|
||||
FMRUKSpawnActor SpawnActor{};
|
||||
if (SelectSpawnActorFromSpawnGroup(Anchor, SpawnGroup, RandomStream, SpawnActor))
|
||||
{
|
||||
if (!SpawnActor.Actor)
|
||||
{
|
||||
UE_LOG(LogMRUK, Error, TEXT("Actor to spawn is a nullptr for label %s. Skipping it."), *Label);
|
||||
return nullptr;
|
||||
}
|
||||
return SpawnAnchorActor(Anchor, SpawnActor);
|
||||
}
|
||||
UE_LOG(LogMRUK, Error, TEXT("Actor is nullptr for label %s."), *Label);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TArray<AActor*> AMRUKAnchorActorSpawner::SpawnAnchorActorsInRoom_Implementation(AMRUKRoom* Room, const FRandomStream& RandomStream)
|
||||
{
|
||||
TArray<AActor*> SpawnedActorsInRoom;
|
||||
|
||||
SpawnedActorsInRoom.Append(SpawnProceduralMeshesInRoom(Room));
|
||||
|
||||
for (const auto& Anchor : Room->AllAnchors)
|
||||
{
|
||||
if (!IsValid(Anchor))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const FString& Label : Anchor->SemanticClassifications)
|
||||
{
|
||||
FMRUKSpawnGroup SpawnGroup{};
|
||||
if (!ShouldSpawnActorForAnchor(Anchor, Label, SpawnGroup))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (AActor* SpawnedActor = SpawnAnchorActorForLabel(Anchor, Label, SpawnGroup, RandomStream))
|
||||
{
|
||||
SpawnedActorsInRoom.Push(SpawnedActor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SpawnedActorsInRoom;
|
||||
}
|
||||
|
||||
void AMRUKAnchorActorSpawner::SpawnActors(AMRUKRoom* Room)
|
||||
{
|
||||
if (!IsValid(Room))
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Can not spawn actors in Room that is a nullptr"));
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveActors(Room);
|
||||
|
||||
// Use last seed if possible to keep spawning deterministic after the first spawn.
|
||||
// In case the anchor random spawn seed has been changed it will be used instead
|
||||
// of the last seed.
|
||||
int32 Seed = -1;
|
||||
if (LastSeed >= 0)
|
||||
{
|
||||
if ((AnchorRandomSpawnSeed >= 0) && (LastSeed != AnchorRandomSpawnSeed))
|
||||
{
|
||||
Seed = AnchorRandomSpawnSeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
Seed = LastSeed;
|
||||
}
|
||||
}
|
||||
else if (AnchorRandomSpawnSeed >= 0)
|
||||
{
|
||||
Seed = AnchorRandomSpawnSeed;
|
||||
}
|
||||
|
||||
FRandomStream RandomStream(Seed);
|
||||
if (Seed < 0)
|
||||
{
|
||||
RandomStream.GenerateNewSeed();
|
||||
}
|
||||
LastSeed = RandomStream.GetCurrentSeed();
|
||||
const TArray<AActor*>& Actors = SpawnAnchorActorsInRoom(Room, RandomStream);
|
||||
SpawnedActors.Add(Room, Actors);
|
||||
|
||||
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
Subsystem->OnRoomUpdated.AddUniqueDynamic(this, &AMRUKAnchorActorSpawner::OnRoomUpdated);
|
||||
Subsystem->OnRoomRemoved.AddUniqueDynamic(this, &AMRUKAnchorActorSpawner::OnRoomRemoved);
|
||||
|
||||
OnActorsSpawned.Broadcast(Room);
|
||||
}
|
||||
|
||||
void AMRUKAnchorActorSpawner::GetSpawnedActorsByRoom(AMRUKRoom* Room, TArray<AActor*>& Actors)
|
||||
{
|
||||
if (const TArray<AActor*>* A = SpawnedActors.Find(Room))
|
||||
{
|
||||
Actors.Append(*A);
|
||||
}
|
||||
}
|
||||
|
||||
void AMRUKAnchorActorSpawner::GetSpawnedActors(TArray<AActor*>& Actors)
|
||||
{
|
||||
for (const auto& KeyValue : SpawnedActors)
|
||||
{
|
||||
Actors.Append(KeyValue.Value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,591 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitBPLibrary.h"
|
||||
|
||||
#include "MRUtilityKit.h"
|
||||
#include "Generated/MRUtilityKitShared.h"
|
||||
#include "MRUtilityKitAnchor.h"
|
||||
#include "MRUtilityKitSubsystem.h"
|
||||
#include "MRUtilityKitSerializationHelpers.h"
|
||||
#include "ProceduralMeshComponent.h"
|
||||
#include "VectorUtil.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "TextureResource.h"
|
||||
#include "Engine/TextureRenderTarget2D.h"
|
||||
|
||||
#include "Engine/Texture2D.h"
|
||||
#include "Serialization/JsonReader.h"
|
||||
#include "Serialization/JsonSerializer.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
TArray<FVector> RecalculateNormals(const TArray<FVector>& Vertices, const TArray<uint32>& Triangles)
|
||||
{
|
||||
TArray<FVector> Normals;
|
||||
|
||||
// Initialize the normals array with zero vectors
|
||||
Normals.Init(FVector::ZeroVector, Vertices.Num());
|
||||
|
||||
// Iterate through each triangle
|
||||
for (int32 TriIndex = 0; TriIndex < Triangles.Num(); TriIndex += 3)
|
||||
{
|
||||
// Get the vertices of the triangle
|
||||
FVector VertexA = Vertices[Triangles[TriIndex]];
|
||||
FVector VertexB = Vertices[Triangles[TriIndex + 1]];
|
||||
FVector VertexC = Vertices[Triangles[TriIndex + 2]];
|
||||
|
||||
// Calculate the triangle's normal
|
||||
const FVector TriangleNormal = FVector::CrossProduct(VertexC - VertexA, VertexB - VertexA).GetSafeNormal();
|
||||
|
||||
// Add the triangle's normal to each of its vertices' normals
|
||||
Normals[Triangles[TriIndex]] += TriangleNormal;
|
||||
Normals[Triangles[TriIndex + 1]] += TriangleNormal;
|
||||
Normals[Triangles[TriIndex + 2]] += TriangleNormal;
|
||||
}
|
||||
|
||||
// Normalize the vertex normals
|
||||
for (FVector& Normal : Normals)
|
||||
{
|
||||
if (!Normal.IsNearlyZero())
|
||||
{
|
||||
Normal.Normalize();
|
||||
}
|
||||
else
|
||||
{
|
||||
Normal = FVector::UpVector;
|
||||
}
|
||||
}
|
||||
|
||||
return Normals;
|
||||
}
|
||||
|
||||
TArray<FProcMeshTangent> RecalculateTangents(const TArray<FVector>& Normals)
|
||||
{
|
||||
TArray<FProcMeshTangent> Tangents;
|
||||
|
||||
// Initialize the tangents array with zero tangents
|
||||
Tangents.Init(FProcMeshTangent(0.f, 0.f, 0.f), Normals.Num());
|
||||
|
||||
// Iterate through each normal
|
||||
for (int32 NormalIndex = 0; NormalIndex < Normals.Num(); NormalIndex++)
|
||||
{
|
||||
const FVector& Normal = Normals[NormalIndex];
|
||||
|
||||
// Calculate a tangent based on the normal
|
||||
FVector TangentX = FVector(1.0f, 0.0f, 0.0f);
|
||||
|
||||
// Gram-Schmidt orthogonalization
|
||||
TangentX -= Normal * FVector::DotProduct(TangentX, Normal);
|
||||
if (!TangentX.IsNearlyZero())
|
||||
{
|
||||
TangentX.Normalize();
|
||||
}
|
||||
else
|
||||
{
|
||||
TangentX = FVector::UpVector;
|
||||
}
|
||||
|
||||
// Store the tangent in the array
|
||||
Tangents[NormalIndex] = FProcMeshTangent(TangentX, false);
|
||||
}
|
||||
|
||||
return Tangents;
|
||||
}
|
||||
|
||||
void SetScaleRecursivelyAdjustingForRotationInternal(USceneComponent* SceneComponent, const FVector& UnRotatedScale, const FQuat& AccumulatedRotation, const FVector& ParentReciprocalScale)
|
||||
{
|
||||
if (SceneComponent)
|
||||
{
|
||||
const auto RelativeRotation = SceneComponent->GetRelativeRotationCache().RotatorToQuat(SceneComponent->GetRelativeRotation());
|
||||
const auto Rotation = AccumulatedRotation * RelativeRotation;
|
||||
const FVector RotatedXAxis = Rotation.GetAxisX();
|
||||
const FVector RotatedYAxis = Rotation.GetAxisY();
|
||||
const FVector RotatedZAxis = Rotation.GetAxisZ();
|
||||
FVector RotatedScale;
|
||||
if (FMath::Abs(RotatedXAxis.X) >= UE_INV_SQRT_2)
|
||||
{
|
||||
RotatedScale.X = UnRotatedScale.X;
|
||||
}
|
||||
else if (FMath::Abs(RotatedXAxis.Y) >= UE_INV_SQRT_2)
|
||||
{
|
||||
RotatedScale.X = UnRotatedScale.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
RotatedScale.X = UnRotatedScale.Z;
|
||||
}
|
||||
|
||||
if (FMath::Abs(RotatedYAxis.X) >= UE_INV_SQRT_2)
|
||||
{
|
||||
RotatedScale.Y = UnRotatedScale.X;
|
||||
}
|
||||
else if (FMath::Abs(RotatedYAxis.Y) >= UE_INV_SQRT_2)
|
||||
{
|
||||
RotatedScale.Y = UnRotatedScale.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
RotatedScale.Y = UnRotatedScale.Z;
|
||||
}
|
||||
|
||||
if (FMath::Abs(RotatedZAxis.X) >= UE_INV_SQRT_2)
|
||||
{
|
||||
RotatedScale.Z = UnRotatedScale.X;
|
||||
}
|
||||
else if (FMath::Abs(RotatedZAxis.Y) >= UE_INV_SQRT_2)
|
||||
{
|
||||
RotatedScale.Z = UnRotatedScale.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
RotatedScale.Z = UnRotatedScale.Z;
|
||||
}
|
||||
|
||||
const FVector OldScale = SceneComponent->GetRelativeScale3D();
|
||||
const FVector NewScale = ParentReciprocalScale * RotatedScale * OldScale;
|
||||
SceneComponent->SetRelativeScale3D(NewScale);
|
||||
const FVector NewParentReciprocalScale = ParentReciprocalScale * (OldScale / NewScale);
|
||||
for (auto Child : SceneComponent->GetAttachChildren())
|
||||
{
|
||||
if (Child)
|
||||
{
|
||||
SetScaleRecursivelyAdjustingForRotationInternal(Child, UnRotatedScale, Rotation, NewParentReciprocalScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TArray<FVector> GeneratePoints(const FTransform& Plane, const FBox2D& PlaneBounds, double PointsPerUnitX, double PointsPerUnitY, double WorldToMeters = 100.0)
|
||||
{
|
||||
const FVector PlaneRight = Plane.GetRotation().GetRightVector();
|
||||
const FVector PlaneUp = Plane.GetRotation().GetUpVector();
|
||||
const FVector PlaneSize = FVector(PlaneBounds.GetSize().X, PlaneBounds.GetSize().Y, 0.0);
|
||||
const FVector PlaneBottomLeft = Plane.GetLocation() - PlaneRight * PlaneSize.X * 0.5f - PlaneUp * PlaneSize.Y * 0.5f;
|
||||
|
||||
const int32 PointsX = FMath::Max(FMathf::Ceil(PointsPerUnitX * PlaneSize.X) / WorldToMeters, 1);
|
||||
const int32 PointsY = FMath::Max(FMathf::Ceil(PointsPerUnitY * PlaneSize.Y) / WorldToMeters, 1);
|
||||
|
||||
const FVector2D Stride{ PlaneSize.X / (PointsX + 1), PlaneSize.Y / (PointsY + 1) };
|
||||
|
||||
TArray<FVector> Points;
|
||||
Points.SetNum(PointsX * PointsY);
|
||||
|
||||
for (int Iy = 0; Iy < PointsY; ++Iy)
|
||||
{
|
||||
for (int Ix = 0; Ix < PointsX; ++Ix)
|
||||
{
|
||||
const float Dx = (Ix + 1) * Stride.X;
|
||||
const float Dy = (Iy + 1) * Stride.Y;
|
||||
const FVector Point = PlaneBottomLeft + Dx * PlaneRight + Dy * PlaneUp;
|
||||
Points[Ix + Iy * PointsX] = Point;
|
||||
}
|
||||
}
|
||||
|
||||
return Points;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UMRUKLoadFromDevice* UMRUKLoadFromDevice::LoadSceneFromDeviceAsync(const UObject* WorldContext
|
||||
)
|
||||
{
|
||||
// We must have a valid contextual world for this action, so we don't even make it
|
||||
// unless we can resolve the UWorld from WorldContext.
|
||||
UWorld* World = GEngine->GetWorldFromContextObject(WorldContext, EGetWorldErrorMode::ReturnNull);
|
||||
if (!ensureAlwaysMsgf(IsValid(WorldContext), TEXT("World Context was not valid.")))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create a new UMyDelayAsyncAction, and store function arguments in it.
|
||||
UMRUKLoadFromDevice* NewAction = NewObject<UMRUKLoadFromDevice>();
|
||||
NewAction->World = World;
|
||||
NewAction->RegisterWithGameInstance(World->GetGameInstance());
|
||||
return NewAction;
|
||||
}
|
||||
|
||||
void UMRUKLoadFromDevice::Activate()
|
||||
{
|
||||
const auto Subsystem = World->GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
Subsystem->OnSceneLoaded.AddDynamic(this, &UMRUKLoadFromDevice::OnSceneLoaded);
|
||||
|
||||
{
|
||||
Subsystem->LoadSceneFromDevice();
|
||||
}
|
||||
}
|
||||
|
||||
void UMRUKLoadFromDevice::OnSceneLoaded(bool Succeeded)
|
||||
{
|
||||
const auto Subsystem = World->GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
Subsystem->OnSceneLoaded.RemoveDynamic(this, &UMRUKLoadFromDevice::OnSceneLoaded);
|
||||
if (Succeeded)
|
||||
{
|
||||
Success.Broadcast();
|
||||
}
|
||||
else
|
||||
{
|
||||
Failure.Broadcast();
|
||||
}
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
bool UMRUKBPLibrary::LoadGlobalMeshFromDevice(FOculusXRUInt64 SpaceHandle, UProceduralMeshComponent* OutProceduralMesh, bool LoadCollision, const UObject* WorldContext)
|
||||
{
|
||||
ensure(OutProceduralMesh);
|
||||
|
||||
const UWorld* World = GEngine->GetWorldFromContextObject(WorldContext, EGetWorldErrorMode::ReturnNull);
|
||||
if (!ensureAlwaysMsgf(IsValid(WorldContext), TEXT("World Context was not valid.")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto RoomLayoutManager = World->GetGameInstance()->GetSubsystem<UMRUKSubsystem>()->GetRoomLayoutManager();
|
||||
const bool LoadResult = RoomLayoutManager->LoadTriangleMesh(SpaceHandle.Value, OutProceduralMesh, LoadCollision);
|
||||
if (!LoadResult)
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Could not load triangle mesh from layout manager"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UMRUKBPLibrary::LoadGlobalMeshFromJsonString(const FString& JsonString, FOculusXRUUID AnchorUUID, UProceduralMeshComponent* OutProceduralMesh, bool LoadCollision)
|
||||
{
|
||||
ensure(OutProceduralMesh);
|
||||
|
||||
TSharedPtr<FJsonValue> JsonValue;
|
||||
auto JsonReader = TJsonReaderFactory<>::Create(JsonString);
|
||||
if (!FJsonSerializer::Deserialize(JsonReader, JsonValue))
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Could not deserialize global mesh JSON data"));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto JsonObject = JsonValue->AsObject();
|
||||
|
||||
// Find room
|
||||
auto RoomsJson = JsonObject->GetArrayField(TEXT("Rooms"));
|
||||
for (const auto& RoomJson : RoomsJson)
|
||||
{
|
||||
auto RoomObject = RoomJson->AsObject();
|
||||
FOculusXRUUID RoomUUID;
|
||||
MRUKDeserialize(*RoomObject->GetField<EJson::None>(TEXT("UUID")), RoomUUID);
|
||||
if (RoomUUID == AnchorUUID)
|
||||
{
|
||||
// Find global mesh anchor
|
||||
auto AnchorsJson = RoomObject->GetArrayField(TEXT("Anchors"));
|
||||
for (const auto& AnchorJson : AnchorsJson)
|
||||
{
|
||||
auto AnchorObject = AnchorJson->AsObject();
|
||||
if (AnchorObject->HasField(TEXT("GlobalMesh")))
|
||||
{
|
||||
auto GlobalMeshObject = AnchorObject->GetField<EJson::Object>(TEXT("GlobalMesh"))->AsObject();
|
||||
|
||||
auto PositionsJson = GlobalMeshObject->GetArrayField(TEXT("Positions"));
|
||||
TArray<FVector> Positions;
|
||||
Positions.Reserve(PositionsJson.Num());
|
||||
for (const auto& PositionJson : PositionsJson)
|
||||
{
|
||||
FVector Position;
|
||||
MRUKDeserialize(*PositionJson, Position);
|
||||
Positions.Push(Position);
|
||||
}
|
||||
|
||||
auto IndicesJson = GlobalMeshObject->GetArrayField(TEXT("Indices"));
|
||||
TArray<int32> Indices;
|
||||
Indices.Reserve(IndicesJson.Num());
|
||||
for (const auto& IndexJson : IndicesJson)
|
||||
{
|
||||
double Index;
|
||||
MRUKDeserialize(*IndexJson, Index);
|
||||
Indices.Push((int32)Index);
|
||||
}
|
||||
|
||||
TArray<FVector> EmptyNormals;
|
||||
TArray<FVector2D> EmptyUV;
|
||||
TArray<FColor> EmptyVertexColors;
|
||||
TArray<FProcMeshTangent> EmptyTangents;
|
||||
OutProceduralMesh->CreateMeshSection(0, Positions, Indices, EmptyNormals, EmptyUV, EmptyVertexColors, EmptyTangents, LoadCollision);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Could not find global mesh in room"));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void UMRUKBPLibrary::RecalculateProceduralMeshAndTangents(UProceduralMeshComponent* Mesh)
|
||||
{
|
||||
if (!IsValid(Mesh))
|
||||
return;
|
||||
|
||||
for (int s = 0; s < Mesh->GetNumSections(); ++s)
|
||||
{
|
||||
FProcMeshSection* Section = Mesh->GetProcMeshSection(s);
|
||||
|
||||
// Get vertices of the section
|
||||
TArray<FVector> Vertices;
|
||||
for (FProcMeshVertex Vertex : Section->ProcVertexBuffer)
|
||||
{
|
||||
Vertices.Add(Vertex.Position);
|
||||
}
|
||||
|
||||
// Calculate normals and tangents
|
||||
TArray<FVector> Normals = RecalculateNormals(Vertices, Section->ProcIndexBuffer);
|
||||
TArray<FProcMeshTangent> Tangents = RecalculateTangents(Normals);
|
||||
TArray<FVector2D> EmptyUV;
|
||||
TArray<FColor> EmptyVertexColors;
|
||||
|
||||
// Update mesh section
|
||||
Mesh->UpdateMeshSection(s, Vertices, Normals, EmptyUV, EmptyVertexColors, Tangents);
|
||||
}
|
||||
}
|
||||
|
||||
bool UMRUKBPLibrary::IsUnrealEngineMetaFork()
|
||||
{
|
||||
#if defined(WITH_OCULUS_BRANCH)
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
FVector2D UMRUKBPLibrary::ComputeCentroid(const TArray<FVector2D>& PolygonPoints)
|
||||
{
|
||||
FVector2D Centroid = FVector2D::ZeroVector;
|
||||
|
||||
double SignedArea = 0.0;
|
||||
for (int32 I = 0; I < PolygonPoints.Num(); ++I)
|
||||
{
|
||||
const double X0 = PolygonPoints[I].X;
|
||||
const double Y0 = PolygonPoints[I].Y;
|
||||
const double X1 = PolygonPoints[(I + 1) % PolygonPoints.Num()].X;
|
||||
const double Y1 = PolygonPoints[(I + 1) % PolygonPoints.Num()].Y;
|
||||
|
||||
const double A = X0 * Y1 - X1 * Y0;
|
||||
SignedArea += A;
|
||||
|
||||
Centroid.X += (X0 + X1) * A;
|
||||
Centroid.Y += (Y0 + Y1) * A;
|
||||
}
|
||||
|
||||
return Centroid / (6.0 * (SignedArea * 0.5));
|
||||
}
|
||||
|
||||
void UMRUKBPLibrary::SetScaleRecursivelyAdjustingForRotation(USceneComponent* SceneComponent, const FVector& UnRotatedScale)
|
||||
{
|
||||
SetScaleRecursivelyAdjustingForRotationInternal(SceneComponent, UnRotatedScale, FQuat::Identity, FVector::OneVector);
|
||||
}
|
||||
|
||||
FVector UMRUKBPLibrary::ComputeDirectionAwayFromClosestWall(const AMRUKAnchor* Anchor, int& OutCardinalAxisIndex, const TArray<int> ExcludedAxes)
|
||||
{
|
||||
double ClosestWallDistance = DBL_MAX;
|
||||
FVector AwayFromWall{};
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
if (ExcludedAxes.Contains(i))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Shoot a ray along the cardinal directions
|
||||
// The "Up" (i.e. Z axis) for anchors typically points away from the facing direction, but it depends
|
||||
// entirely on how the user defined the volume in scene capture.
|
||||
const auto CardinalAxis = (FQuat::MakeFromEuler({ 0.0, 0.0, 90.0 * i }).RotateVector(Anchor->GetActorUpVector()));
|
||||
|
||||
for (const auto& WallAnchor : Anchor->Room->WallAnchors)
|
||||
{
|
||||
if (!WallAnchor)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
FMRUKHit Hit{};
|
||||
if (!WallAnchor->Raycast(Anchor->GetActorLocation(), CardinalAxis, 0.0, Hit))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const auto DistToWall = FVector::Distance(Hit.HitPosition, Anchor->GetActorLocation());
|
||||
if (DistToWall < ClosestWallDistance)
|
||||
{
|
||||
ClosestWallDistance = DistToWall;
|
||||
AwayFromWall = -CardinalAxis;
|
||||
OutCardinalAxisIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return AwayFromWall;
|
||||
}
|
||||
|
||||
UTexture2D* UMRUKBPLibrary::ConstructTexture2D(UTextureRenderTarget2D* RenderTarget2D, UObject* Outer, const FString& TexName)
|
||||
{
|
||||
const auto SizeX = RenderTarget2D->SizeX;
|
||||
const auto SizeY = RenderTarget2D->SizeY;
|
||||
const auto Tex = UTexture2D::CreateTransient(SizeX, SizeY, RenderTarget2D->GetFormat());
|
||||
Tex->AddToRoot();
|
||||
Tex->Filter = TF_Bilinear;
|
||||
Tex->CompressionSettings = TC_Default;
|
||||
Tex->SRGB = 0;
|
||||
Tex->UpdateResource();
|
||||
|
||||
FTextureRenderTargetResource* RenderTargetResource = RenderTarget2D->GameThread_GetRenderTargetResource();
|
||||
FReadSurfaceDataFlags ReadSurfaceDataFlags;
|
||||
ReadSurfaceDataFlags.SetLinearToGamma(false);
|
||||
|
||||
TArray<FColor> OutBMP;
|
||||
RenderTargetResource->ReadPixels(OutBMP, ReadSurfaceDataFlags);
|
||||
|
||||
FTexture2DMipMap& Mip = Tex->GetPlatformData()->Mips[0];
|
||||
void* Data = Mip.BulkData.Lock(LOCK_READ_WRITE);
|
||||
FMemory::Memcpy(Data, OutBMP.GetData(), SizeX * SizeY * 4);
|
||||
Mip.BulkData.Unlock();
|
||||
|
||||
Tex->UpdateResource();
|
||||
return Tex;
|
||||
}
|
||||
|
||||
FLinearColor UMRUKBPLibrary::GetMatrixColumn(const FMatrix& Matrix, int32 Index)
|
||||
{
|
||||
ensure(0 <= Index && Index < 4);
|
||||
FLinearColor V;
|
||||
V.R = Matrix.M[0][Index];
|
||||
V.G = Matrix.M[1][Index];
|
||||
V.B = Matrix.M[2][Index];
|
||||
V.A = Matrix.M[3][Index];
|
||||
return V;
|
||||
}
|
||||
|
||||
TArray<FVector> UMRUKBPLibrary::ComputeRoomBoxGrid(const AMRUKRoom* Room, int32 MaxPointsCount, double PointsPerUnitX, double PointsPerUnitY)
|
||||
{
|
||||
TArray<FVector> AllPoints;
|
||||
|
||||
const double WorldToMeters = Room->GetWorld()->GetWorldSettings()->WorldToMeters;
|
||||
for (const AMRUKAnchor* WallAnchor : Room->WallAnchors)
|
||||
{
|
||||
|
||||
const auto Points = GeneratePoints(WallAnchor->GetTransform(), WallAnchor->PlaneBounds, PointsPerUnitX, PointsPerUnitY, WorldToMeters);
|
||||
AllPoints.Append(Points);
|
||||
}
|
||||
|
||||
// Generate points between floor and ceiling
|
||||
const float DistFloorCeiling = Room->CeilingAnchor->GetTransform().GetLocation().Z - Room->FloorAnchor->GetTransform().GetLocation().Z;
|
||||
const int32 PlanesCount = FMath::Max(FMathf::Ceil(PointsPerUnitY * DistFloorCeiling) / WorldToMeters, 1);
|
||||
const int32 SpaceBetweenPlanes = DistFloorCeiling / PlanesCount;
|
||||
for (int i = 1; i < PlanesCount; ++i)
|
||||
{
|
||||
FTransform Transform = Room->CeilingAnchor->GetTransform();
|
||||
Transform.SetLocation(FVector(Transform.GetLocation().X, Transform.GetLocation().Y, Transform.GetLocation().Z - (SpaceBetweenPlanes * i)));
|
||||
const auto Points = GeneratePoints(Transform, Room->CeilingAnchor->PlaneBounds, PointsPerUnitX, PointsPerUnitY, WorldToMeters);
|
||||
AllPoints.Append(Points);
|
||||
}
|
||||
|
||||
const auto CeilingPoints = GeneratePoints(Room->CeilingAnchor->GetTransform(), Room->CeilingAnchor->PlaneBounds, PointsPerUnitX, PointsPerUnitY, WorldToMeters);
|
||||
AllPoints.Append(CeilingPoints);
|
||||
|
||||
const auto FloorPoints = GeneratePoints(Room->FloorAnchor->GetTransform(), Room->FloorAnchor->PlaneBounds, PointsPerUnitX, PointsPerUnitY, WorldToMeters);
|
||||
AllPoints.Append(FloorPoints);
|
||||
|
||||
if (AllPoints.Num() > MaxPointsCount)
|
||||
{
|
||||
// Shuffle the array
|
||||
AllPoints.Sort([](const FVector& /*Item1*/, const FVector& /*Item2*/) {
|
||||
return FMath::FRand() < 0.5f;
|
||||
});
|
||||
|
||||
// Randomly remove some points
|
||||
int32 PointsToRemoveCount = AllPoints.Num() - MaxPointsCount;
|
||||
while (PointsToRemoveCount > 0)
|
||||
{
|
||||
AllPoints.Pop();
|
||||
--PointsToRemoveCount;
|
||||
}
|
||||
}
|
||||
return AllPoints;
|
||||
}
|
||||
|
||||
void UMRUKBPLibrary::CreateMeshSegmentation(const TArray<FVector>& MeshPositions, const TArray<uint32>& MeshIndices,
|
||||
const TArray<FVector>& SegmentationPoints, const FVector& ReservedMin, const FVector& ReservedMax,
|
||||
TArray<FMRUKMeshSegment>& OutSegments, FMRUKMeshSegment& OutReservedSegment)
|
||||
{
|
||||
if (!MRUKShared::GetInstance())
|
||||
{
|
||||
UE_LOG(LogMRUK, Error, TEXT("MRUK shared library is not available. To use this functionality make sure the library is included"));
|
||||
return;
|
||||
}
|
||||
|
||||
TArray<FVector3f> MeshPositionsF;
|
||||
MeshPositionsF.Reserve(MeshPositions.Num());
|
||||
for (const FVector& V : MeshPositions)
|
||||
{
|
||||
MeshPositionsF.Add(FVector3f(V));
|
||||
}
|
||||
|
||||
TArray<FVector3f> SegmentationPointsF;
|
||||
SegmentationPointsF.Reserve(SegmentationPoints.Num());
|
||||
for (const FVector& V : SegmentationPoints)
|
||||
{
|
||||
SegmentationPointsF.Add(FVector3f(V));
|
||||
}
|
||||
|
||||
MRUKShared::MrukMesh3f* MeshSegmentsF = nullptr;
|
||||
uint32_t MeshSegmentsCount = 0;
|
||||
|
||||
MRUKShared::MrukMesh3f ReservedMeshSegmentF{};
|
||||
|
||||
const FVector3f ReservedMinF(ReservedMin);
|
||||
const FVector3f ReservedMaxF(ReservedMax);
|
||||
|
||||
MRUKShared::GetInstance()->ComputeMeshSegmentation(MeshPositionsF.GetData(), MeshPositionsF.Num(), MeshIndices.GetData(),
|
||||
MeshIndices.Num(), SegmentationPointsF.GetData(), SegmentationPointsF.Num(), ReservedMinF, ReservedMaxF, &MeshSegmentsF,
|
||||
&MeshSegmentsCount, &ReservedMeshSegmentF);
|
||||
|
||||
OutSegments.Reserve(MeshSegmentsCount);
|
||||
for (uint32_t i = 0; i < MeshSegmentsCount; ++i)
|
||||
{
|
||||
const MRUKShared::MrukMesh3f& SegmentF = MeshSegmentsF[i];
|
||||
if (SegmentF.numIndices == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FMRUKMeshSegment MeshSegment{};
|
||||
MeshSegment.Indices.Reserve(SegmentF.numIndices);
|
||||
MeshSegment.Positions.Reserve(SegmentF.numVertices);
|
||||
for (uint32_t j = 0; j < SegmentF.numIndices; ++j)
|
||||
{
|
||||
MeshSegment.Indices.Add(SegmentF.indices[j]);
|
||||
}
|
||||
for (uint32_t j = 0; j < SegmentF.numVertices; ++j)
|
||||
{
|
||||
const FVector3f& V = SegmentF.vertices[j];
|
||||
MeshSegment.Positions.Add({ V.X, V.Y, V.Z });
|
||||
}
|
||||
|
||||
OutSegments.Emplace(MoveTemp(MeshSegment));
|
||||
}
|
||||
|
||||
if (ReservedMeshSegmentF.numIndices && ReservedMeshSegmentF.numVertices)
|
||||
{
|
||||
OutReservedSegment.Indices.Reserve(ReservedMeshSegmentF.numIndices);
|
||||
OutReservedSegment.Positions.Reserve(ReservedMeshSegmentF.numVertices);
|
||||
for (uint32_t j = 0; j < ReservedMeshSegmentF.numIndices; ++j)
|
||||
{
|
||||
OutReservedSegment.Indices.Add(ReservedMeshSegmentF.indices[j]);
|
||||
}
|
||||
for (uint32_t j = 0; j < ReservedMeshSegmentF.numVertices; ++j)
|
||||
{
|
||||
const FVector3f& V = ReservedMeshSegmentF.vertices[j];
|
||||
OutReservedSegment.Positions.Add({ V.X, V.Y, V.Z });
|
||||
}
|
||||
}
|
||||
|
||||
MRUKShared::GetInstance()->FreeMeshSegmentation(MeshSegmentsF, MeshSegmentsCount, &ReservedMeshSegmentF);
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitBlobShadowComponent.h"
|
||||
#include "MRUtilityKitTelemetry.h"
|
||||
#include "MRUtilityKit.h"
|
||||
#include "Kismet/KismetSystemLibrary.h"
|
||||
#include "UObject/ConstructorHelpers.h"
|
||||
#include "Materials/MaterialInstance.h"
|
||||
#include "Materials/MaterialInstanceDynamic.h"
|
||||
#include "Engine/StaticMesh.h"
|
||||
|
||||
UMRUKBlobShadowComponent::UMRUKBlobShadowComponent()
|
||||
{
|
||||
const ConstructorHelpers::FObjectFinder<UStaticMesh> PlaneAsset(TEXT("/Engine/BasicShapes/Plane"));
|
||||
if (PlaneAsset.Succeeded())
|
||||
{
|
||||
SetStaticMesh(PlaneAsset.Object);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogMRUK, Log, TEXT("Blob shadow couldn't find plane mesh in /Engine/BasicShapes/Plane"));
|
||||
}
|
||||
|
||||
const ConstructorHelpers::FObjectFinder<UMaterialInstance> BlobShadowMaterialAsset(TEXT("/OculusXR/Materials/MI_BlobShadow"));
|
||||
if (BlobShadowMaterialAsset.Succeeded())
|
||||
{
|
||||
SetMaterial(0, BlobShadowMaterialAsset.Object);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogMRUK, Log, TEXT("Blob shadow couldn't find blob shadow material in /OculusXR/Materials/MI_BlobShadow"));
|
||||
}
|
||||
|
||||
// Prevent sorting issue with transparent ground
|
||||
SetTranslucentSortPriority(1);
|
||||
|
||||
// We don't want any collision
|
||||
SetCollisionProfileName("NoCollision");
|
||||
|
||||
// Need tick to be enabled
|
||||
SetComponentTickEnabled(true);
|
||||
PrimaryComponentTick.bCanEverTick = true;
|
||||
bAutoActivate = true;
|
||||
}
|
||||
|
||||
void UMRUKBlobShadowComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadBlobShadowMarker> Event(static_cast<int>(GetTypeHash(this)));
|
||||
|
||||
// Create dynamic material (for roundness and gradient settings)
|
||||
DynMaterial = CreateAndSetMaterialInstanceDynamic(0);
|
||||
|
||||
// Since we're updating the component size and position every frame it's better to not be influenced by parent
|
||||
SetUsingAbsoluteLocation(true);
|
||||
SetUsingAbsoluteRotation(true);
|
||||
SetUsingAbsoluteScale(true);
|
||||
|
||||
// Compute size and position once
|
||||
UpdatePlaneSizeAndPosition();
|
||||
}
|
||||
|
||||
void UMRUKBlobShadowComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
|
||||
// Update component size and position every frame
|
||||
UpdatePlaneSizeAndPosition();
|
||||
}
|
||||
|
||||
void UMRUKBlobShadowComponent::UpdatePlaneSizeAndPosition()
|
||||
{
|
||||
FVector Origin;
|
||||
FVector2D Extent;
|
||||
double Yaw;
|
||||
ComputeOwner2DBounds(Origin, Extent, Yaw);
|
||||
|
||||
Extent += FVector2D::UnitVector * ExtraExtent; // Additional extent
|
||||
SetWorldScale3D(FVector(Extent * 0.02f, 1.f)); // Plane mesh is 100x100, multiplying by 0.02f to match the correct size when scaling
|
||||
SetWorldRotation(FRotator(0.f, Yaw, 0.f));
|
||||
|
||||
// Sphere trace to the ground
|
||||
FHitResult Hit;
|
||||
TArray<AActor*> ActorsToIgnore;
|
||||
ActorsToIgnore.Add(GetOwner());
|
||||
const bool bHasHit = UKismetSystemLibrary::SphereTraceSingle(this, Origin, Origin + FVector::DownVector * MaxVerticalDistance, Extent.Length() * 0.5f, TraceTypeQuery1,
|
||||
true, ActorsToIgnore, EDrawDebugTrace::None, Hit, true);
|
||||
float Opacity = 0.f;
|
||||
if (bHasHit)
|
||||
{
|
||||
SetHiddenInGame(false); // Make plane visible
|
||||
SetWorldLocation(Hit.ImpactPoint + FVector::UpVector * 0.02f); // Impact + some offset to avoid Z-fighting
|
||||
Opacity = FMath::GetMappedRangeValueClamped(
|
||||
FVector2D(MaxVerticalDistance - FadeDistance, MaxVerticalDistance),
|
||||
FVector2D(1.f, 0.f),
|
||||
Hit.Distance); // Set opacity based on distance to ground
|
||||
}
|
||||
else
|
||||
SetHiddenInGame(true); // Hide plane
|
||||
|
||||
// Update material's parameters
|
||||
if (DynMaterial)
|
||||
{
|
||||
DynMaterial->SetScalarParameterValue("CornerWorldSize", FMath::Min(Extent.X, Extent.Y) * Roundness);
|
||||
DynMaterial->SetScalarParameterValue("Gradient", Gradient);
|
||||
DynMaterial->SetScalarParameterValue("GradientPower", GradientPower);
|
||||
DynMaterial->SetScalarParameterValue("Opacity", Opacity);
|
||||
}
|
||||
else // In case DynMaterial doesn't exist (e.g. in editor), update values directly on the mesh
|
||||
{
|
||||
SetScalarParameterValueOnMaterials("CornerWorldSize", FMath::Min(Extent.X, Extent.Y) * Roundness);
|
||||
SetScalarParameterValueOnMaterials("Gradient", Gradient);
|
||||
SetScalarParameterValueOnMaterials("GradientPower", GradientPower);
|
||||
SetScalarParameterValueOnMaterials("Opacity", Opacity);
|
||||
}
|
||||
}
|
||||
|
||||
void UMRUKBlobShadowComponent::ComputeOwner2DBounds(FVector& Origin, FVector2D& Extent, double& Yaw) const
|
||||
{
|
||||
const AActor* Actor = GetOwner();
|
||||
|
||||
// Calculate local space BoundingBox from all components, but keep yaw to have a correct 2D bounding box at the end
|
||||
FBox Box(ForceInit);
|
||||
const FRotator YawOnly = FRotator(0.f, Actor->GetActorRotation().Yaw, 0.f);
|
||||
const FTransform ActorToWorld = FTransform(YawOnly.Quaternion());
|
||||
const FTransform WorldToActor = ActorToWorld.Inverse();
|
||||
|
||||
Actor->ForEachComponent<UPrimitiveComponent>(true, [&](const UPrimitiveComponent* InPrimComp) {
|
||||
// Ignore editor & blob shadow components
|
||||
if (InPrimComp->IsRegistered() && !InPrimComp->IsEditorOnly() && !InPrimComp->bUseAttachParentBound && !InPrimComp->IsA<UMRUKBlobShadowComponent>())
|
||||
{
|
||||
const FTransform ComponentToActor = InPrimComp->GetComponentTransform() * WorldToActor;
|
||||
Box += InPrimComp->CalcBounds(ComponentToActor).GetBox();
|
||||
}
|
||||
});
|
||||
|
||||
const FTransform Transform = Actor->GetTransform();
|
||||
// Project 3D extent to 2D
|
||||
const FVector ProjectedExtent = FVector::VectorPlaneProject(Box.GetExtent(), FVector::UpVector);
|
||||
|
||||
Origin = ActorToWorld.TransformPosition(Box.GetCenter());
|
||||
Extent = FVector2D(ProjectedExtent);
|
||||
Yaw = Transform.GetRotation().Rotator().Yaw;
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitDebugComponent.h"
|
||||
#include "MRUtilityKitTelemetry.h"
|
||||
#include "MRUtilityKitSubsystem.h"
|
||||
#include "MRUtilityKitAnchor.h"
|
||||
#include "Kismet/KismetMathLibrary.h"
|
||||
#include "IXRTrackingSystem.h"
|
||||
#include "TextRenderComponent.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/Engine.h"
|
||||
|
||||
UMRUKDebugComponent::UMRUKDebugComponent()
|
||||
{
|
||||
PrimaryComponentTick.bCanEverTick = true;
|
||||
}
|
||||
|
||||
void UMRUKDebugComponent::ShowAnchorAtRayHit(const FVector& Origin, const FVector& Direction)
|
||||
{
|
||||
if (!GizmoActorClass)
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Can not show anchor because no gizmo actor is set"));
|
||||
return;
|
||||
}
|
||||
|
||||
HideAnchor();
|
||||
|
||||
const auto Subsystem = GetOwner()->GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
if (!Subsystem)
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Can not show anchor because there is no MRUtilityKit subsystem"));
|
||||
return;
|
||||
}
|
||||
|
||||
FMRUKHit Hit{};
|
||||
FMRUKLabelFilter LabelFilter{};
|
||||
auto Anchor = Subsystem->Raycast(Origin, Direction, 0.0, LabelFilter, Hit);
|
||||
if (!Anchor)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Spawn Gizmo
|
||||
if (!ActiveGizmoActor)
|
||||
{
|
||||
FActorSpawnParameters ActorSpawnParams;
|
||||
ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||||
ActorSpawnParams.Owner = GetOwner();
|
||||
ActiveGizmoActor = GetWorld()->SpawnActor(GizmoActorClass, nullptr, ActorSpawnParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
ActiveGizmoActor->SetActorHiddenInGame(false);
|
||||
}
|
||||
ActiveGizmoActor->SetActorLocation(Hit.HitPosition);
|
||||
ActiveGizmoActor->SetActorScale3D(GizmoScale);
|
||||
ActiveGizmoActor->SetActorRotation(Anchor->GetActorRotation());
|
||||
|
||||
if (!TextActorClass)
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Can not show text at anchor because no text actor is set"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Spawn Text
|
||||
if (!ActiveTextActor)
|
||||
{
|
||||
FActorSpawnParameters ActorSpawnParams;
|
||||
ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||||
ActorSpawnParams.Owner = GetOwner();
|
||||
ActiveTextActor = GetWorld()->SpawnActor(TextActorClass, nullptr, ActorSpawnParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
ActiveTextActor->SetActorHiddenInGame(false);
|
||||
}
|
||||
|
||||
auto TextRenderComponent = ActiveTextActor->GetComponentByClass<UTextRenderComponent>();
|
||||
FString Text;
|
||||
for (int i = 0; i < Anchor->SemanticClassifications.Num(); ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
Text += ", ";
|
||||
}
|
||||
Text += Anchor->SemanticClassifications[i];
|
||||
}
|
||||
TextRenderComponent->SetText(FText::FromString(Text));
|
||||
|
||||
ActiveTextActor->SetActorLocation(Hit.HitPosition + (Hit.HitNormal * 20.0));
|
||||
ActiveTextActor->SetActorScale3D(TextScale);
|
||||
OrientTextActorToPlayer();
|
||||
|
||||
SetComponentTickEnabled(true);
|
||||
}
|
||||
|
||||
void UMRUKDebugComponent::HideAnchor()
|
||||
{
|
||||
if (ActiveGizmoActor)
|
||||
{
|
||||
ActiveGizmoActor->SetActorHiddenInGame(true);
|
||||
}
|
||||
if (ActiveTextActor)
|
||||
{
|
||||
ActiveTextActor->SetActorHiddenInGame(true);
|
||||
}
|
||||
SetComponentTickEnabled(false);
|
||||
}
|
||||
|
||||
void UMRUKDebugComponent::ShowAnchorSpaceAtRayHit(const FVector& Origin, const FVector& Direction)
|
||||
{
|
||||
const auto Subsystem = GetOwner()->GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
if (!Subsystem)
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Can not show anchor because there is no MRUtilityKit subsystem"));
|
||||
return;
|
||||
}
|
||||
|
||||
FMRUKHit Hit{};
|
||||
const auto Anchor = Subsystem->Raycast(Origin, Direction, 0.0, {}, Hit);
|
||||
if (!Anchor)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ActiveAnchorSpaceActor || (ActiveAnchorSpaceActor && ActiveAnchorSpaceActor->GetParentActor() != Anchor))
|
||||
{
|
||||
static constexpr double DebugSpaceOffset = 0.5;
|
||||
|
||||
HideAnchorSpace();
|
||||
|
||||
FActorSpawnParameters ActorSpawnParams;
|
||||
ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||||
ActorSpawnParams.Owner = Anchor;
|
||||
ActiveAnchorSpaceActor = GetWorld()->SpawnActor<AActor>(ActorSpawnParams);
|
||||
ActiveAnchorSpaceActor->SetRootComponent(NewObject<USceneComponent>(ActiveAnchorSpaceActor, TEXT("SceneComponent")));
|
||||
ActiveAnchorSpaceActor->AttachToActor(Anchor, FAttachmentTransformRules::KeepRelativeTransform);
|
||||
ActiveAnchorSpaceActor->GetRootComponent()->SetMobility(EComponentMobility::Movable);
|
||||
|
||||
const auto ProceduralMesh = NewObject<UProceduralMeshComponent>(ActiveAnchorSpaceActor, TEXT("DebugVolumePlane"));
|
||||
Anchor->GenerateProceduralAnchorMesh(ProceduralMesh, {}, {}, true, false, DebugSpaceOffset);
|
||||
ActiveAnchorSpaceActor->AddInstanceComponent(ProceduralMesh);
|
||||
ProceduralMesh->SetupAttachment(ActiveAnchorSpaceActor->GetRootComponent());
|
||||
ProceduralMesh->RegisterComponent();
|
||||
}
|
||||
}
|
||||
|
||||
void UMRUKDebugComponent::HideAnchorSpace()
|
||||
{
|
||||
if (ActiveAnchorSpaceActor)
|
||||
{
|
||||
ActiveAnchorSpaceActor->Destroy();
|
||||
ActiveAnchorSpaceActor = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void UMRUKDebugComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadDebugComponentMarker> Event(static_cast<int>(GetTypeHash(this)));
|
||||
|
||||
SetComponentTickEnabled(false);
|
||||
}
|
||||
|
||||
void UMRUKDebugComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
|
||||
UE_LOG(LogTemp, Warning, TEXT("Ticking enabled"));
|
||||
OrientTextActorToPlayer();
|
||||
}
|
||||
|
||||
void UMRUKDebugComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
Super::EndPlay(EndPlayReason);
|
||||
|
||||
if (ActiveGizmoActor)
|
||||
{
|
||||
ActiveGizmoActor->Destroy();
|
||||
ActiveGizmoActor = nullptr;
|
||||
}
|
||||
if (ActiveTextActor)
|
||||
{
|
||||
ActiveTextActor->Destroy();
|
||||
ActiveTextActor = nullptr;
|
||||
}
|
||||
if (ActiveAnchorSpaceActor)
|
||||
{
|
||||
ActiveAnchorSpaceActor->Destroy();
|
||||
ActiveAnchorSpaceActor = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void UMRUKDebugComponent::OrientTextActorToPlayer() const
|
||||
{
|
||||
if (ActiveTextActor)
|
||||
{
|
||||
FQuat Orientation;
|
||||
FVector Position(0.0);
|
||||
GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, Orientation, Position);
|
||||
const auto TextForward = (Position - ActiveTextActor->GetActorLocation()).GetSafeNormal();
|
||||
const auto TextUp = FVector::UpVector;
|
||||
const auto TextRot = UKismetMathLibrary::MakeRotFromXZ(TextForward, TextUp);
|
||||
ActiveTextActor->SetActorRotation(TextRot);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitDestructibleMesh.h"
|
||||
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "MRUtilityKitBPLibrary.h"
|
||||
#include "MRUtilityKitSubsystem.h"
|
||||
#include "MRUtilityKitAnchor.h"
|
||||
#include "MRUtilityKitRoom.h"
|
||||
#include "MRUtilityKitTelemetry.h"
|
||||
#include "OculusXRTelemetry.h"
|
||||
#include "Tasks/Task.h"
|
||||
|
||||
constexpr const char* RESERVED_MESH_SEGMENT_TAG = "ReservedMeshSegment";
|
||||
|
||||
UMRUKDestructibleMeshComponent::UMRUKDestructibleMeshComponent(const FObjectInitializer& ObjectInitializer)
|
||||
: UProceduralMeshComponent(ObjectInitializer)
|
||||
{
|
||||
PrimaryComponentTick.bCanEverTick = true;
|
||||
}
|
||||
|
||||
void UMRUKDestructibleMeshComponent::SegmentMesh(const TArray<FVector>& MeshPositions, const TArray<uint32>& MeshIndices, const TArray<FVector>& SegmentationPoints)
|
||||
{
|
||||
TaskResult = UE::Tasks::Launch(UE_SOURCE_LOCATION, [this, MeshPositions, MeshIndices, SegmentationPoints]() {
|
||||
TArray<FMRUKMeshSegment> Segments;
|
||||
FMRUKMeshSegment ReservedMeshSegment;
|
||||
|
||||
const FVector ReservedMin(ReservedTop, -1.0, -1.0);
|
||||
const FVector ReservedMax(ReservedBottom, -1.0, -1.0);
|
||||
|
||||
UMRUKBPLibrary::CreateMeshSegmentation(MeshPositions, MeshIndices, SegmentationPoints, ReservedMin, ReservedMax, Segments, ReservedMeshSegment);
|
||||
return TPair<TArray<FMRUKMeshSegment>, FMRUKMeshSegment>{ MoveTemp(Segments), MoveTemp(ReservedMeshSegment) };
|
||||
});
|
||||
SetComponentTickEnabled(true);
|
||||
}
|
||||
|
||||
void UMRUKDestructibleMeshComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
SetComponentTickEnabled(false);
|
||||
}
|
||||
|
||||
void UMRUKDestructibleMeshComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
|
||||
if (!TaskResult.IsCompleted())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& [MeshSegments, ReservedMeshSegment] = TaskResult.GetResult();
|
||||
|
||||
for (int32 i = 0; i < MeshSegments.Num(); ++i)
|
||||
{
|
||||
const auto& [Positions, Indices] = MeshSegments[i];
|
||||
const FString ProcMeshName = FString::Printf(TEXT("DestructibleMeshSegment%d"), i);
|
||||
const auto ProcMesh = NewObject<UProceduralMeshComponent>(GetOwner(), *ProcMeshName);
|
||||
const FAttachmentTransformRules TransformRules{ EAttachmentRule::KeepRelative, false };
|
||||
ProcMesh->AttachToComponent(GetOwner()->GetRootComponent(), TransformRules);
|
||||
ProcMesh->RegisterComponent();
|
||||
ProcMesh->ComponentTags.AddUnique(TEXT("DestructibleMeshSegment"));
|
||||
GetOwner()->AddInstanceComponent(ProcMesh);
|
||||
ProcMesh->CreateMeshSection(0, Positions, Indices, {}, {}, {}, {}, true);
|
||||
if (GlobalMeshMaterial)
|
||||
{
|
||||
ProcMesh->SetMaterial(0, GlobalMeshMaterial);
|
||||
}
|
||||
}
|
||||
if (ReservedMeshSegment.Indices.Num() > 0)
|
||||
{
|
||||
const auto ProcMesh = NewObject<UProceduralMeshComponent>(GetOwner(), TEXT("ReservedMeshSegment"));
|
||||
const FAttachmentTransformRules TransformRules{ EAttachmentRule::KeepRelative, false };
|
||||
ProcMesh->AttachToComponent(GetOwner()->GetRootComponent(), TransformRules);
|
||||
ProcMesh->RegisterComponent();
|
||||
ProcMesh->ComponentTags.AddUnique(RESERVED_MESH_SEGMENT_TAG);
|
||||
GetOwner()->AddInstanceComponent(ProcMesh);
|
||||
ProcMesh->CreateMeshSection(0, ReservedMeshSegment.Positions, ReservedMeshSegment.Indices, {}, {}, {}, {}, true);
|
||||
if (GlobalMeshMaterial)
|
||||
{
|
||||
ProcMesh->SetMaterial(0, GlobalMeshMaterial);
|
||||
}
|
||||
}
|
||||
|
||||
SetComponentTickEnabled(false);
|
||||
|
||||
OnMeshesGenerated.Broadcast();
|
||||
}
|
||||
|
||||
AMRUKDestructibleGlobalMesh::AMRUKDestructibleGlobalMesh()
|
||||
{
|
||||
DestructibleMeshComponent = CreateDefaultSubobject<UMRUKDestructibleMeshComponent>(TEXT("DestructibleMesh"));
|
||||
SetRootComponent(DestructibleMeshComponent);
|
||||
}
|
||||
|
||||
void AMRUKDestructibleGlobalMesh::CreateDestructibleMesh(AMRUKRoom* Room)
|
||||
{
|
||||
const UMRUKSubsystem* MRUKSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
if (!Room)
|
||||
{
|
||||
Room = MRUKSubsystem->GetCurrentRoom();
|
||||
}
|
||||
if (!Room)
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Could not find a room for the destructible mesh"));
|
||||
return;
|
||||
}
|
||||
if (!Room->GlobalMeshAnchor)
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("No global mesh available for creating a destructible mesh"));
|
||||
return;
|
||||
}
|
||||
const AMRUKAnchor* GlobalMesh = Room->GlobalMeshAnchor;
|
||||
UProceduralMeshComponent* GlobalProcMesh = Cast<UProceduralMeshComponent>(GlobalMesh->GetComponentByClass(UProceduralMeshComponent::StaticClass()));
|
||||
if (!GlobalProcMesh)
|
||||
{
|
||||
Room->LoadGlobalMeshFromDevice();
|
||||
}
|
||||
if (!GlobalProcMesh)
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Could not load a triangle mesh from the global mesh"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Attach to the global mesh
|
||||
const FAttachmentTransformRules AttachmentTransformRules{ EAttachmentRule::KeepRelative, false };
|
||||
AttachToActor(Room->GlobalMeshAnchor, AttachmentTransformRules);
|
||||
|
||||
// Get global mesh data
|
||||
ensure(GlobalProcMesh);
|
||||
ensure(GlobalProcMesh->ComponentHasTag("GlobalMesh"));
|
||||
FProcMeshSection* ProcMeshSection = GlobalProcMesh->GetProcMeshSection(0);
|
||||
TArray<FVector> MeshPositions;
|
||||
MeshPositions.SetNum(ProcMeshSection->ProcVertexBuffer.Num());
|
||||
for (int32 i = 0; i < ProcMeshSection->ProcVertexBuffer.Num(); ++i)
|
||||
{
|
||||
MeshPositions[i] = ProcMeshSection->ProcVertexBuffer[i].Position * GetWorldSettings()->WorldToMeters;
|
||||
}
|
||||
const TArray<uint32>& MeshIndices = ProcMeshSection->ProcIndexBuffer;
|
||||
|
||||
TArray<FVector> SegmentationPointsWS = UMRUKBPLibrary::ComputeRoomBoxGrid(Room, MaxPointsCount, PointsPerUnitX, PointsPerUnitY);
|
||||
|
||||
TArray<FVector> SegmentationPointsLS;
|
||||
SegmentationPointsLS.SetNum(SegmentationPointsWS.Num());
|
||||
const FTransform T = GlobalMesh->GetActorTransform().Inverse();
|
||||
for (int32 i = 0; i < SegmentationPointsWS.Num(); ++i)
|
||||
{
|
||||
SegmentationPointsLS[i] = T.TransformPosition(SegmentationPointsWS[i]);
|
||||
}
|
||||
DestructibleMeshComponent->SegmentMesh(MeshPositions, MeshIndices, SegmentationPointsLS);
|
||||
}
|
||||
|
||||
void AMRUKDestructibleGlobalMesh::RemoveGlobalMeshSegment(UPrimitiveComponent* Mesh)
|
||||
{
|
||||
if (!Mesh->ComponentTags.Contains(RESERVED_MESH_SEGMENT_TAG))
|
||||
{
|
||||
// Only remove mesh segments that are allowed to be destroyed
|
||||
Mesh->DestroyComponent();
|
||||
}
|
||||
}
|
||||
|
||||
void AMRUKDestructibleGlobalMeshSpawner::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadDestructibleGlobalMeshSpawner> Event(static_cast<int>(GetTypeHash(this)));
|
||||
|
||||
if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly)
|
||||
{
|
||||
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
if (Subsystem->SceneLoadStatus == EMRUKInitStatus::Complete)
|
||||
{
|
||||
AddDestructibleGlobalMesh(Subsystem->GetCurrentRoom());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only listen for the room created event in case no current room was available yet
|
||||
Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKDestructibleGlobalMeshSpawner::OnRoomCreated);
|
||||
// Remove destructible meshes as soon as the room gets removed
|
||||
Subsystem->OnRoomRemoved.AddUniqueDynamic(this, &AMRUKDestructibleGlobalMeshSpawner::OnRoomRemoved);
|
||||
}
|
||||
}
|
||||
else if (SpawnMode == EMRUKSpawnMode::AllRooms)
|
||||
{
|
||||
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
for (auto Room : Subsystem->Rooms)
|
||||
{
|
||||
AddDestructibleGlobalMesh(Room);
|
||||
}
|
||||
|
||||
// Listen for new rooms that get created
|
||||
Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKDestructibleGlobalMeshSpawner::OnRoomCreated);
|
||||
// Remove destructible meshes as soon as the room gets removed
|
||||
Subsystem->OnRoomRemoved.AddUniqueDynamic(this, &AMRUKDestructibleGlobalMeshSpawner::OnRoomRemoved);
|
||||
}
|
||||
}
|
||||
|
||||
void AMRUKDestructibleGlobalMeshSpawner::OnRoomCreated(AMRUKRoom* Room)
|
||||
{
|
||||
if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly && GetGameInstance()->GetSubsystem<UMRUKSubsystem>()->GetCurrentRoom() != Room)
|
||||
{
|
||||
// Skip this room if it is not the current room
|
||||
return;
|
||||
}
|
||||
AddDestructibleGlobalMesh(Room);
|
||||
}
|
||||
|
||||
void AMRUKDestructibleGlobalMeshSpawner::OnRoomRemoved(AMRUKRoom* Room)
|
||||
{
|
||||
if (!IsValid(Room))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (AMRUKDestructibleGlobalMesh** Mesh = SpawnedMeshes.Find(Room))
|
||||
{
|
||||
(*Mesh)->Destroy();
|
||||
SpawnedMeshes.Remove(Room);
|
||||
}
|
||||
}
|
||||
|
||||
AMRUKDestructibleGlobalMesh* AMRUKDestructibleGlobalMeshSpawner::FindDestructibleMeshForRoom(AMRUKRoom* Room)
|
||||
{
|
||||
if (AMRUKDestructibleGlobalMesh** Mesh = SpawnedMeshes.Find(Room))
|
||||
{
|
||||
return *Mesh;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AMRUKDestructibleGlobalMesh* AMRUKDestructibleGlobalMeshSpawner::AddDestructibleGlobalMesh(AMRUKRoom* Room)
|
||||
{
|
||||
if (SpawnedMeshes.Contains(Room))
|
||||
{
|
||||
return SpawnedMeshes[Room];
|
||||
}
|
||||
|
||||
AMRUKDestructibleGlobalMesh* Mesh = GetWorld()->SpawnActor<AMRUKDestructibleGlobalMesh>();
|
||||
Mesh->PointsPerUnitX = PointsPerUnitX;
|
||||
Mesh->PointsPerUnitY = PointsPerUnitY;
|
||||
Mesh->MaxPointsCount = MaxPointsCount;
|
||||
Mesh->DestructibleMeshComponent->GlobalMeshMaterial = GlobalMeshMaterial;
|
||||
Mesh->DestructibleMeshComponent->ReservedBottom = ReservedBottom;
|
||||
Mesh->DestructibleMeshComponent->ReservedTop = ReservedTop;
|
||||
Mesh->CreateDestructibleMesh();
|
||||
SpawnedMeshes.Add(Room, Mesh);
|
||||
|
||||
return Mesh;
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitDistanceMapGenerator.h"
|
||||
#include "MRUtilityKitSubsystem.h"
|
||||
#include "MRUtilityKit.h"
|
||||
#include "MRUtilityKitRoom.h"
|
||||
#include "MRUtilityKitAnchor.h"
|
||||
#include "Components/SceneCaptureComponent2D.h"
|
||||
#include "Engine/CanvasRenderTarget2D.h"
|
||||
#include "Engine/Canvas.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Kismet/KismetRenderingLibrary.h"
|
||||
#include "Materials/MaterialInstanceDynamic.h"
|
||||
#include "Materials/MaterialInterface.h"
|
||||
#include "UObject/ConstructorHelpers.h"
|
||||
|
||||
AMRUKDistanceMapGenerator::AMRUKDistanceMapGenerator()
|
||||
{
|
||||
// Create components
|
||||
|
||||
Root = CreateDefaultSubobject<USceneComponent>(TEXT("DefaultSceneRoot"));
|
||||
SceneCapture2D = CreateDefaultSubobject<USceneCaptureComponent2D>(TEXT("SceneCapture2D"));
|
||||
|
||||
RootComponent = Root;
|
||||
|
||||
// Setup components
|
||||
|
||||
SceneCapture2D->SetupAttachment(Root);
|
||||
SceneCapture2D->ProjectionType = ECameraProjectionMode::Orthographic;
|
||||
SceneCapture2D->OrthoWidth = 512.0f;
|
||||
SceneCapture2D->CaptureSource = ESceneCaptureSource::SCS_SceneColorHDR;
|
||||
SceneCapture2D->PrimitiveRenderMode = ESceneCapturePrimitiveRenderMode::PRM_UseShowOnlyList;
|
||||
SceneCapture2D->bCaptureEveryFrame = false;
|
||||
SceneCapture2D->bCaptureOnMovement = false;
|
||||
|
||||
const ConstructorHelpers::FObjectFinder<UCanvasRenderTarget2D> RT1Finder(TEXT("/OculusXR/Textures/CRT_JumpFlood1"));
|
||||
if (RT1Finder.Succeeded())
|
||||
{
|
||||
RenderTarget1 = RT1Finder.Object;
|
||||
}
|
||||
const ConstructorHelpers::FObjectFinder<UCanvasRenderTarget2D> RT2Finder(TEXT("/OculusXR/Textures/CRT_JumpFlood2"));
|
||||
if (RT2Finder.Succeeded())
|
||||
{
|
||||
RenderTarget2 = RT2Finder.Object;
|
||||
}
|
||||
const ConstructorHelpers::FObjectFinder<UCanvasRenderTarget2D> RTMaskFinder(TEXT("/OculusXR/Textures/CRT_Mask"));
|
||||
if (RTMaskFinder.Succeeded())
|
||||
{
|
||||
SceneCapture2D->TextureTarget = RTMaskFinder.Object;
|
||||
}
|
||||
const ConstructorHelpers::FObjectFinder<UCanvasRenderTarget2D> RTDistanceMapFinder(TEXT("/OculusXR/Textures/CRT_DistanceMap"));
|
||||
if (RTDistanceMapFinder.Succeeded())
|
||||
{
|
||||
DistanceMapRenderTarget = RTDistanceMapFinder.Object;
|
||||
}
|
||||
|
||||
const ConstructorHelpers::FObjectFinder<UMaterialInterface> MaskMaterialFinder(TEXT("/OculusXR/Materials/M_CreateMask"));
|
||||
if (MaskMaterialFinder.Succeeded())
|
||||
{
|
||||
MaskMaterial = MaskMaterialFinder.Object;
|
||||
}
|
||||
const ConstructorHelpers::FObjectFinder<UMaterialInterface> JFPassMaterialFinder(TEXT("/OculusXR/Materials/M_JFAPass"));
|
||||
if (JFPassMaterialFinder.Succeeded())
|
||||
{
|
||||
JFPassMaterial = JFPassMaterialFinder.Object;
|
||||
}
|
||||
|
||||
const ConstructorHelpers::FObjectFinder<UMaterialInterface> SceneObjectMaskMaterialFinder(TEXT("/OculusXR/Materials/M_SceneObjectMask"));
|
||||
if (SceneObjectMaskMaterialFinder.Succeeded())
|
||||
{
|
||||
SceneObjectMaskMaterial = SceneObjectMaskMaterialFinder.Object;
|
||||
}
|
||||
|
||||
const ConstructorHelpers::FObjectFinder<UMaterialInterface> FloorMaskMaterialFinder(TEXT("/OculusXR/Materials/M_FloorMask"));
|
||||
if (FloorMaskMaterialFinder.Succeeded())
|
||||
{
|
||||
FloorMaskMaterial = FloorMaskMaterialFinder.Object;
|
||||
}
|
||||
|
||||
const ConstructorHelpers::FObjectFinder<UMaterialInterface> DistanceMapFreeSpaceMaterialFinder(TEXT("/OculusXR/Materials/M_DistanceMapFree"));
|
||||
if (DistanceMapFreeSpaceMaterialFinder.Succeeded())
|
||||
{
|
||||
DistanceMapFreeSpaceMaterial = DistanceMapFreeSpaceMaterialFinder.Object;
|
||||
}
|
||||
|
||||
const ConstructorHelpers::FObjectFinder<UMaterialInterface> DistanceMapOccupiedSpaceMaterialFinder(TEXT("/OculusXR/Materials/M_DistanceMapOccupied"));
|
||||
if (DistanceMapOccupiedSpaceMaterialFinder.Succeeded())
|
||||
{
|
||||
DistanceMapOccupiedSpaceMaterial = DistanceMapOccupiedSpaceMaterialFinder.Object;
|
||||
}
|
||||
|
||||
const ConstructorHelpers::FObjectFinder<UMaterialInterface> DistanceMapAllSpaceMaterialFinder(TEXT("/OculusXR/Materials/M_DistanceMapAll"));
|
||||
if (DistanceMapAllSpaceMaterialFinder.Succeeded())
|
||||
{
|
||||
DistanceMapAllSpaceMaterial = DistanceMapAllSpaceMaterialFinder.Object;
|
||||
}
|
||||
}
|
||||
|
||||
void AMRUKDistanceMapGenerator::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
SceneObjectMaskMaterial->EnsureIsComplete();
|
||||
FloorMaskMaterial->EnsureIsComplete();
|
||||
|
||||
if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly)
|
||||
{
|
||||
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
if (AMRUKRoom* CurrentRoom = Subsystem->GetCurrentRoom())
|
||||
{
|
||||
CreateMaskMeshesForRoom(CurrentRoom);
|
||||
}
|
||||
else
|
||||
{
|
||||
Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKDistanceMapGenerator::OnRoomCreated);
|
||||
}
|
||||
}
|
||||
else if (SpawnMode == EMRUKSpawnMode::AllRooms)
|
||||
{
|
||||
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
for (auto& Room : Subsystem->Rooms)
|
||||
{
|
||||
CreateMaskMeshesForRoom(Room);
|
||||
}
|
||||
|
||||
Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKDistanceMapGenerator::OnRoomCreated);
|
||||
}
|
||||
}
|
||||
|
||||
UTexture* AMRUKDistanceMapGenerator::CaptureDistanceMap()
|
||||
{
|
||||
CaptureInitialSceneMask();
|
||||
RenderDistanceMap();
|
||||
return GetDistanceMap();
|
||||
}
|
||||
|
||||
void AMRUKDistanceMapGenerator::CaptureInitialSceneMask()
|
||||
{
|
||||
if (!JFPassMaterialInstance)
|
||||
{
|
||||
JFPassMaterialInstance = UMaterialInstanceDynamic::Create(JFPassMaterial, this);
|
||||
}
|
||||
if (DistanceMapFreeSpaceMaterial && !DistanceMapFreeSpaceMaterialInstance)
|
||||
{
|
||||
DistanceMapFreeSpaceMaterialInstance = UMaterialInstanceDynamic::Create(DistanceMapFreeSpaceMaterial, this);
|
||||
}
|
||||
if (DistanceMapOccupiedSpaceMaterial && !DistanceMapOccupiedSpaceMaterialInstance)
|
||||
{
|
||||
DistanceMapOccupiedSpaceMaterialInstance = UMaterialInstanceDynamic::Create(DistanceMapOccupiedSpaceMaterial, this);
|
||||
}
|
||||
if (DistanceMapAllSpaceMaterial && !DistanceMapAllSpaceMaterialInstance)
|
||||
{
|
||||
DistanceMapAllSpaceMaterialInstance = UMaterialInstanceDynamic::Create(DistanceMapAllSpaceMaterial, this);
|
||||
}
|
||||
|
||||
check(SceneCapture2D->TextureTarget->SizeX == SceneCapture2D->TextureTarget->SizeY);
|
||||
|
||||
SceneCapture2D->CaptureScene();
|
||||
|
||||
// Renders the texture that was captured by the scene capture component into a mask that can then be used further down
|
||||
|
||||
UKismetRenderingLibrary::ClearRenderTarget2D(GetWorld(), RenderTarget1, FLinearColor::Black);
|
||||
|
||||
UCanvas* Canvas{};
|
||||
FVector2D Size{};
|
||||
FDrawToRenderTargetContext RenderTargetContext{};
|
||||
UKismetRenderingLibrary::BeginDrawCanvasToRenderTarget(GetWorld(), RenderTarget1, Canvas, Size, RenderTargetContext);
|
||||
|
||||
Canvas->K2_DrawMaterial(MaskMaterial, FVector2D::ZeroVector, Size, FVector2D::ZeroVector);
|
||||
|
||||
UKismetRenderingLibrary::EndDrawCanvasToRenderTarget(GetWorld(), RenderTargetContext);
|
||||
}
|
||||
|
||||
void AMRUKDistanceMapGenerator::RenderDistanceMap()
|
||||
{
|
||||
UCanvasRenderTarget2D* RTs[2] = { RenderTarget1, RenderTarget2 };
|
||||
|
||||
int32 RTIndex = 0;
|
||||
|
||||
const double TextureSize = SceneCapture2D->TextureTarget->SizeX;
|
||||
|
||||
check(TextureSize == RenderTarget1->SizeX);
|
||||
check(TextureSize == RenderTarget1->SizeY);
|
||||
check(TextureSize == RenderTarget2->SizeX);
|
||||
check(TextureSize == RenderTarget2->SizeY);
|
||||
|
||||
const int32 LastIndex = static_cast<int32>(FMath::Log2(TextureSize / 2.0));
|
||||
|
||||
// Play buffer ping pong and execute the jump flood algorithm on each step
|
||||
|
||||
for (int32 I = 1; I <= LastIndex; ++I)
|
||||
{
|
||||
// Read from the render target that we have written before
|
||||
JFPassMaterialInstance->SetTextureParameterValue(FName("RT"), RTs[RTIndex]);
|
||||
const double Step = 1.0 / FMath::Pow(2.0, static_cast<double>(I));
|
||||
JFPassMaterialInstance->SetScalarParameterValue(FName("Step"), Step);
|
||||
|
||||
// Make sure to render to the other render target
|
||||
RTIndex = (RTIndex + 1) % 2;
|
||||
UCanvasRenderTarget2D* RT = RTs[RTIndex];
|
||||
|
||||
UKismetRenderingLibrary::ClearRenderTarget2D(GetWorld(), RT, FLinearColor::Black);
|
||||
|
||||
UCanvas* Canvas{};
|
||||
FVector2D Size{};
|
||||
FDrawToRenderTargetContext RenderTargetContext{};
|
||||
UKismetRenderingLibrary::BeginDrawCanvasToRenderTarget(GetWorld(), RT, Canvas, Size, RenderTargetContext);
|
||||
|
||||
Canvas->K2_DrawMaterial(JFPassMaterialInstance, FVector2D::ZeroVector, Size, FVector2D::ZeroVector);
|
||||
|
||||
UKismetRenderingLibrary::EndDrawCanvasToRenderTarget(GetWorld(), RenderTargetContext);
|
||||
}
|
||||
|
||||
DistanceMapRT = RTIndex;
|
||||
|
||||
if (DistanceMapGenerationMode != EMRUKDistanceMapGenerationMode::None)
|
||||
{
|
||||
UMaterialInstanceDynamic* RenderMaterial = nullptr;
|
||||
|
||||
UKismetRenderingLibrary::ClearRenderTarget2D(GetWorld(), DistanceMapRenderTarget);
|
||||
UCanvas* Canvas{};
|
||||
FVector2D Size{};
|
||||
FDrawToRenderTargetContext RenderTargetContext{};
|
||||
UKismetRenderingLibrary::BeginDrawCanvasToRenderTarget(GetWorld(), DistanceMapRenderTarget, Canvas, Size, RenderTargetContext);
|
||||
switch (DistanceMapGenerationMode)
|
||||
{
|
||||
case EMRUKDistanceMapGenerationMode::FreeSpace:
|
||||
RenderMaterial = DistanceMapFreeSpaceMaterialInstance;
|
||||
break;
|
||||
case EMRUKDistanceMapGenerationMode::OccupiedSpace:
|
||||
RenderMaterial = DistanceMapOccupiedSpaceMaterialInstance;
|
||||
break;
|
||||
case EMRUKDistanceMapGenerationMode::AllSpace:
|
||||
RenderMaterial = DistanceMapAllSpaceMaterialInstance;
|
||||
break;
|
||||
case EMRUKDistanceMapGenerationMode::None:
|
||||
RenderMaterial = nullptr;
|
||||
break;
|
||||
}
|
||||
if (RenderMaterial)
|
||||
{
|
||||
RenderMaterial->SetTextureParameterValue(FName("RT"), GetDistanceMapRenderTarget());
|
||||
Canvas->K2_DrawMaterial(RenderMaterial, FVector2D::ZeroVector, Size, FVector2D::ZeroVector);
|
||||
}
|
||||
UKismetRenderingLibrary::EndDrawCanvasToRenderTarget(GetWorld(), RenderTargetContext);
|
||||
}
|
||||
}
|
||||
|
||||
void AMRUKDistanceMapGenerator::OnRoomCreated(AMRUKRoom* Room)
|
||||
{
|
||||
if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly && GetGameInstance()->GetSubsystem<UMRUKSubsystem>()->GetCurrentRoom() != Room)
|
||||
{
|
||||
// Skip this room if it is not the current room
|
||||
return;
|
||||
}
|
||||
|
||||
CreateMaskMeshesForRoom(Room);
|
||||
}
|
||||
|
||||
void AMRUKDistanceMapGenerator::OnRoomUpdated(AMRUKRoom* Room)
|
||||
{
|
||||
if (!SpawnedMaskMeshes.Find(Room))
|
||||
{
|
||||
// A room was updated that we don't care about. If we are in current room only mode
|
||||
// we only want to update the one room we created
|
||||
return;
|
||||
}
|
||||
|
||||
CreateMaskMeshesForRoom(Room);
|
||||
}
|
||||
|
||||
void AMRUKDistanceMapGenerator::CreateMaskMeshesForRoom(AMRUKRoom* Room)
|
||||
{
|
||||
if (!Room)
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Can not create masked meshes for room that is a nullptr"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (TArray<AActor*>* Actors = SpawnedMaskMeshes.Find(Room))
|
||||
{
|
||||
for (AActor* Actor : *Actors)
|
||||
{
|
||||
Actor->Destroy();
|
||||
}
|
||||
Actors->Empty();
|
||||
SpawnedMaskMeshes.Remove(Room);
|
||||
}
|
||||
|
||||
// Create for each anchor a mesh with a material to use as a mask
|
||||
// to initialize the jump flood algorithm.
|
||||
|
||||
TArray<AActor*> SpawnedActors;
|
||||
|
||||
for (auto& Anchor : Room->AllAnchors)
|
||||
{
|
||||
if (!Anchor->VolumeBounds.IsValid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
SpawnedActors.Push(CreateMaskMeshOfAnchor(Anchor));
|
||||
}
|
||||
if (Room->FloorAnchor)
|
||||
{
|
||||
SpawnedActors.Push(CreateMaskMeshOfAnchor(Room->FloorAnchor));
|
||||
}
|
||||
|
||||
SpawnedMaskMeshes.Add(Room, SpawnedActors);
|
||||
|
||||
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
Subsystem->OnRoomRemoved.AddUniqueDynamic(this, &AMRUKDistanceMapGenerator::RemoveMaskMeshesFromRoom);
|
||||
Subsystem->OnRoomUpdated.AddUniqueDynamic(this, &AMRUKDistanceMapGenerator::OnRoomUpdated);
|
||||
|
||||
OnReady.Broadcast();
|
||||
}
|
||||
|
||||
AActor* AMRUKDistanceMapGenerator::CreateMaskMeshOfAnchor(AMRUKAnchor* Anchor)
|
||||
{
|
||||
check(Anchor);
|
||||
|
||||
FActorSpawnParameters ActorSpawnParams{};
|
||||
ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||||
ActorSpawnParams.Owner = Anchor;
|
||||
AActor* Actor = GetWorld()->SpawnActor<AActor>(ActorSpawnParams);
|
||||
|
||||
Actor->Tags.Push(GMRUK_DISTANCE_MAP_ACTOR_TAG);
|
||||
|
||||
const auto R = NewObject<USceneComponent>(Actor, TEXT("Root"));
|
||||
Actor->SetRootComponent(R);
|
||||
R->RegisterComponent();
|
||||
Actor->AddInstanceComponent(R);
|
||||
|
||||
Actor->AttachToActor(Anchor, FAttachmentTransformRules::KeepRelativeTransform);
|
||||
|
||||
const auto ProceduralMesh = NewObject<UProceduralMeshComponent>(Actor, TEXT("DistanceMapMesh"));
|
||||
Anchor->GenerateProceduralAnchorMesh(ProceduralMesh, {}, {}, true, false);
|
||||
|
||||
// Set a material depending if the anchor is the floor or a scene object.
|
||||
// The different materials have different colors. These colors will be used to create different
|
||||
// initialization masks for the jump flood algorithm.
|
||||
|
||||
if (Anchor == Anchor->Room->FloorAnchor)
|
||||
{
|
||||
ProceduralMesh->SetMaterial(0, FloorMaskMaterial);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProceduralMesh->SetMaterial(0, SceneObjectMaskMaterial);
|
||||
}
|
||||
|
||||
ProceduralMesh->SetupAttachment(Actor->GetRootComponent());
|
||||
ProceduralMesh->RegisterComponent();
|
||||
Actor->AddInstanceComponent(ProceduralMesh);
|
||||
|
||||
// The created meshes will be only used to create a mask for jump flood.
|
||||
// Therefore we don't want them to show up in the normal camera.
|
||||
// This unfortunate means that the meshes will show up in other scene captures the user may place as well.
|
||||
|
||||
ProceduralMesh->SetVisibleInSceneCaptureOnly(true);
|
||||
SceneCapture2D->ShowOnlyActors.Push(Actor);
|
||||
|
||||
return Actor;
|
||||
}
|
||||
|
||||
AActor* AMRUKDistanceMapGenerator::UpdateMaskMeshOfAnchor(AMRUKAnchor* Anchor)
|
||||
{
|
||||
TArray<AActor*> ChildActors;
|
||||
Anchor->GetAllChildActors(ChildActors);
|
||||
|
||||
for (auto Child : ChildActors)
|
||||
{
|
||||
if (Child->ActorHasTag(GMRUK_DISTANCE_MAP_ACTOR_TAG))
|
||||
{
|
||||
// Remove existing distance map actor
|
||||
SceneCapture2D->ShowOnlyActors.Remove(Child);
|
||||
Child->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
return CreateMaskMeshOfAnchor(Anchor);
|
||||
}
|
||||
|
||||
UTexture* AMRUKDistanceMapGenerator::GetDistanceMap() const
|
||||
{
|
||||
return GetDistanceMapRenderTarget();
|
||||
}
|
||||
|
||||
UCanvasRenderTarget2D* AMRUKDistanceMapGenerator::GetDistanceMapRenderTarget() const
|
||||
{
|
||||
if (DistanceMapRT == -1)
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Make sure to first render the distance map by calling CaptureDistanceMap()"));
|
||||
return nullptr;
|
||||
}
|
||||
check(DistanceMapRT >= 0);
|
||||
|
||||
UCanvasRenderTarget2D* RTs[2] = { RenderTarget1, RenderTarget2 };
|
||||
return RTs[DistanceMapRT];
|
||||
}
|
||||
|
||||
FMinimalViewInfo AMRUKDistanceMapGenerator::GetSceneCaptureView() const
|
||||
{
|
||||
FMinimalViewInfo Info = {};
|
||||
SceneCapture2D->GetCameraView(1.0f, Info);
|
||||
return Info;
|
||||
}
|
||||
|
||||
void AMRUKDistanceMapGenerator::RemoveMaskMeshesFromRoom(AMRUKRoom* Room)
|
||||
{
|
||||
if (!Room)
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Can not remove masked meshes for room that is a nullptr"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (TArray<AActor*>* Actors = SpawnedMaskMeshes.Find(Room))
|
||||
{
|
||||
for (AActor* Actor : *Actors)
|
||||
{
|
||||
Actor->Destroy();
|
||||
}
|
||||
Actors->Empty();
|
||||
SpawnedMaskMeshes.Remove(Room);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitGeometry.h"
|
||||
|
||||
#include "MRUtilityKit.h"
|
||||
#include "Generated/MRUtilityKitShared.h"
|
||||
|
||||
void MRUKTriangulatePolygon(const TArray<TArray<FVector2f>>& Polygons, TArray<FVector2D>& Vertices, TArray<int32>& Indices)
|
||||
{
|
||||
Vertices.Empty();
|
||||
Indices.Empty();
|
||||
|
||||
auto MRUKShared = MRUKShared::GetInstance();
|
||||
if (!MRUKShared)
|
||||
{
|
||||
UE_LOG(LogMRUK, Error, TEXT("MRUK shared library is not available. To use this functionality make sure the library is included"));
|
||||
return;
|
||||
}
|
||||
|
||||
TArray<MRUKShared::MrukPolygon2f> ConvertedPolygons;
|
||||
ConvertedPolygons.Reserve(Polygons.Num());
|
||||
for (const auto& Polygon : Polygons)
|
||||
{
|
||||
ConvertedPolygons.Push({ Polygon.GetData(), static_cast<uint32_t>(Polygon.Num()) });
|
||||
}
|
||||
|
||||
auto Mesh = MRUKShared->TriangulatePolygon(ConvertedPolygons.GetData(), ConvertedPolygons.Num());
|
||||
|
||||
Vertices.Reserve(Mesh.numVertices);
|
||||
Indices.Reserve(Mesh.numIndices);
|
||||
|
||||
for (uint32_t i = 0; i < Mesh.numVertices; ++i)
|
||||
{
|
||||
Vertices.Push(FVector2D(Mesh.vertices[i]));
|
||||
}
|
||||
for (uint32_t i = 0; i < Mesh.numIndices; ++i)
|
||||
{
|
||||
Indices.Push(Mesh.indices[i]);
|
||||
}
|
||||
|
||||
MRUKShared->FreeMesh(&Mesh);
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitGridSliceResizer.h"
|
||||
|
||||
#include "MRUtilityKit.h"
|
||||
#include "MRUtilityKitTelemetry.h"
|
||||
#include "OculusXRTelemetry.h"
|
||||
#include "Engine/StaticMesh.h"
|
||||
#include "ProceduralMeshComponent.h"
|
||||
#include "StaticMeshResources.h"
|
||||
|
||||
UMRUKGridSliceResizerComponent::UMRUKGridSliceResizerComponent()
|
||||
{
|
||||
PrimaryComponentTick.bCanEverTick = true;
|
||||
|
||||
ProcMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("ProcMesh"));
|
||||
ProcMesh->SetupAttachment(this);
|
||||
}
|
||||
|
||||
void UMRUKGridSliceResizerComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadGridSliceResizerMarker> Event(static_cast<int>(GetTypeHash(this)));
|
||||
SliceMesh();
|
||||
}
|
||||
|
||||
void UMRUKGridSliceResizerComponent::OnRegister()
|
||||
{
|
||||
Super::OnRegister();
|
||||
SliceMesh();
|
||||
}
|
||||
|
||||
void UMRUKGridSliceResizerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
|
||||
const FVector ActorScale = GetOwner() ? GetOwner()->GetActorScale() : FVector::OneVector;
|
||||
if (Mesh && ActorScale != ResizerScale)
|
||||
{
|
||||
ResizerScale = ActorScale;
|
||||
SliceMesh();
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
void UMRUKGridSliceResizerComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
||||
{
|
||||
Super::PostEditChangeProperty(PropertyChangedEvent);
|
||||
|
||||
if (PropertyChangedEvent.Property->GetOwner<AActor>() == GetOwner())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FName PropertyName = (PropertyChangedEvent.Property != nullptr) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
|
||||
if (PropertyName == GET_MEMBER_NAME_CHECKED(UMRUKGridSliceResizerComponent, BorderXNegative)
|
||||
|| PropertyName == GET_MEMBER_NAME_CHECKED(UMRUKGridSliceResizerComponent, BorderXPositive)
|
||||
|| PropertyName == GET_MEMBER_NAME_CHECKED(UMRUKGridSliceResizerComponent, BorderYNegative)
|
||||
|| PropertyName == GET_MEMBER_NAME_CHECKED(UMRUKGridSliceResizerComponent, BorderYPositive)
|
||||
|| PropertyName == GET_MEMBER_NAME_CHECKED(UMRUKGridSliceResizerComponent, BorderZNegative)
|
||||
|| PropertyName == GET_MEMBER_NAME_CHECKED(UMRUKGridSliceResizerComponent, BorderZPositive)
|
||||
|| PropertyName == GET_MEMBER_NAME_CHECKED(UMRUKGridSliceResizerComponent, SlicerPivotOffset))
|
||||
{
|
||||
SliceMesh();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void UMRUKGridSliceResizerComponent::SliceMesh()
|
||||
{
|
||||
if (!Mesh)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Mesh->bAllowCPUAccess)
|
||||
{
|
||||
UE_LOG(LogMRUK, Error, TEXT("Can not slice a mesh that has no CPU access. Make sure you enable CPU access on the static mesh asset."));
|
||||
return;
|
||||
}
|
||||
|
||||
TArray<FVector> Positions;
|
||||
TArray<FVector> Normals;
|
||||
TArray<FVector2D> UVs;
|
||||
TArray<FColor> Colors;
|
||||
TArray<int32> Triangles;
|
||||
|
||||
const FStaticMeshLODResources& LODResources = Mesh->GetRenderData()->LODResources[0];
|
||||
const FStaticMeshVertexBuffers& VertexBuffers = LODResources.VertexBuffers;
|
||||
const FRawStaticIndexBuffer& IndexBuffer = LODResources.IndexBuffer;
|
||||
|
||||
Positions.SetNum(LODResources.GetNumVertices());
|
||||
Normals.SetNum(LODResources.GetNumVertices());
|
||||
UVs.SetNum(LODResources.GetNumVertices());
|
||||
Colors.SetNum(LODResources.GetNumVertices());
|
||||
|
||||
const FVector ActorScale = GetOwner() ? GetOwner()->GetActorScale() : FVector::OneVector;
|
||||
const FVector ActorScaleInv = FVector(1.0 / ActorScale.X, 1.0 / ActorScale.Y, 1.0 / ActorScale.Z);
|
||||
const FVector Size = ActorScale;
|
||||
|
||||
// Slicing
|
||||
|
||||
FTransform PivotTransform;
|
||||
PivotTransform.SetLocation(-SlicerPivotOffset);
|
||||
|
||||
FTransform ScaledInvPivotTransform;
|
||||
ScaledInvPivotTransform.SetLocation(Size * SlicerPivotOffset);
|
||||
|
||||
// The bounding box of the mesh to resize
|
||||
FBox BBox = Mesh->GetBoundingBox();
|
||||
BBox = FBox(PivotTransform.TransformPosition(BBox.Min), PivotTransform.TransformPosition(BBox.Max));
|
||||
|
||||
// The bounding box of the mesh to resize scaled by the size
|
||||
const FBox BBoxScaled = FBox(BBox.Min * Size, BBox.Max * Size);
|
||||
|
||||
// The bounding box of the mesh to resize scaled including the pivot point
|
||||
// This may be a bigger box as ScaledBBox in case the pivot is outside of the scaled bounding box.
|
||||
const FBox BBoxScaledPivot = FBox(
|
||||
FVector(FMath::Min(BBox.Min.X, SlicerPivotOffset.X), FMath::Min(BBox.Min.Y, SlicerPivotOffset.Y), FMath::Min(BBox.Min.Z, SlicerPivotOffset.Z)),
|
||||
FVector(FMath::Max(BBox.Max.X, SlicerPivotOffset.X), FMath::Max(BBox.Max.Y, SlicerPivotOffset.Y), FMath::Max(BBox.Max.Z, SlicerPivotOffset.Z)));
|
||||
|
||||
// Locations of the border slices between 0 - 1
|
||||
FVector BorderPos = FVector(BorderXPositive, BorderYPositive, BorderZPositive);
|
||||
FVector BorderNeg = FVector(BorderXNegative, BorderYNegative, BorderZNegative);
|
||||
|
||||
// Locations of the border slices for the X,Y,Z axis in local space
|
||||
FVector BorderPosLS;
|
||||
FVector BorderNegLS;
|
||||
|
||||
// Distance from the Border[Pos|Neg]LS to the outer maximum/minimum of the BBox
|
||||
FVector StubPos;
|
||||
FVector StubNeg;
|
||||
|
||||
// The inner bounding box that should be stretched in all directions
|
||||
FVector BBoxInnerMax;
|
||||
FVector BBoxInnerMin;
|
||||
|
||||
// The expected bounding box of the inner bounding box when its scaled up by the size
|
||||
FVector BBoxInnerScaledMax;
|
||||
FVector BBoxInnerScaledMin;
|
||||
|
||||
// The ratio between the inner bounding box and the scaled bounding box
|
||||
FVector InnerBoxScaleRatioMax;
|
||||
FVector InnerBoxScaleRatioMin;
|
||||
|
||||
// The ratio to use for downscaling in case it's needed
|
||||
FVector DownscaleMax;
|
||||
FVector DownscaleMin;
|
||||
|
||||
for (int32 I = 0; I < 3; ++I)
|
||||
{
|
||||
// We don't want to have division by zero further down the line
|
||||
BorderPos[I] = FMath::Clamp(BorderPos[I], DBL_EPSILON, 1.0);
|
||||
BorderNeg[I] = FMath::Clamp(BorderNeg[I], DBL_EPSILON, 1.0);
|
||||
|
||||
BorderPosLS[I] = BBoxScaledPivot.Max[I] - (1.0 - BorderPos[I]) * FMath::Abs(BBoxScaledPivot.Max[I]);
|
||||
BorderNegLS[I] = BBoxScaledPivot.Min[I] + (1.0 - BorderNeg[I]) * FMath::Abs(BBoxScaledPivot.Min[I]);
|
||||
|
||||
StubPos[I] = FMath::Abs(BBox.Max[I] - BorderPosLS[I]);
|
||||
StubNeg[I] = FMath::Abs(BBox.Min[I] - BorderNegLS[I]);
|
||||
|
||||
BBoxInnerMax[I] = BBox.Max[I] - StubPos[I];
|
||||
BBoxInnerMin[I] = BBox.Min[I] + StubNeg[I];
|
||||
|
||||
// Max may be negative and Min may be positive in case the stubs are greater than
|
||||
// the scaled down bounding box and therefore don't fit the scaled bounding box.
|
||||
// This case gets treated special down below.
|
||||
BBoxInnerScaledMax[I] = BBoxScaled.Max[I] - StubPos[I];
|
||||
BBoxInnerScaledMin[I] = BBoxScaled.Min[I] + StubNeg[I];
|
||||
|
||||
InnerBoxScaleRatioMax[I] = FMath::Max(0.0, BBoxInnerScaledMax[I] / BBoxInnerMax[I]);
|
||||
InnerBoxScaleRatioMin[I] = FMath::Max(0.0, BBoxInnerScaledMin[I] / BBoxInnerMin[I]);
|
||||
|
||||
// When Downscale[Min/Max] needs to be applied the temporary bounding box is
|
||||
// Max == StubPos, Min == StubNeg. Therefore get the ratio between it and the
|
||||
// expected scaled down bounding box to calculate the scale that needs
|
||||
// to be applied
|
||||
DownscaleMax[I] = BBoxScaled.Max[I] / StubPos[I];
|
||||
DownscaleMin[I] = BBoxScaled.Min[I] / StubNeg[I];
|
||||
}
|
||||
|
||||
// Process vertices
|
||||
|
||||
// If the center shouldn't be scaled we need to take care of the case when the original
|
||||
// center vertices would be outside of the expected downscaled bounding box. Therefore, iterate
|
||||
// through all vertices and check if the center vertices are outside. If they are outside we need
|
||||
// to scale down the center part as usually.
|
||||
// This unfortunately has to be done in a separate first pass.
|
||||
bool ScaleDownCenter[3] = { false, false, false };
|
||||
|
||||
const int32 VertexCount = LODResources.GetNumVertices();
|
||||
for (int32 I = 0; I < VertexCount; ++I)
|
||||
{
|
||||
const FVector3f& Normal = VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(I);
|
||||
Normals[I] = FVector(Normal.X, Normal.Y, Normal.Z);
|
||||
|
||||
const FVector2f& UV = VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(I, 0);
|
||||
UVs[I] = FVector2D(UV.X, UV.Y);
|
||||
|
||||
const FVector3f& P = VertexBuffers.PositionVertexBuffer.VertexPosition(I);
|
||||
|
||||
// Apply pivot offset
|
||||
Positions[I] = PivotTransform.TransformPosition(FVector(P.X, P.Y, P.Z));
|
||||
const FVector& Position = Positions[I];
|
||||
|
||||
for (int32 A = 0; A < 3; ++A)
|
||||
{
|
||||
if ((0.0 <= Position[A] && Position[A] <= BorderPosLS[A]) && (Position[A] > BBoxInnerScaledMax[A]))
|
||||
{
|
||||
ScaleDownCenter[A] = true;
|
||||
}
|
||||
else if ((BorderNegLS[A] <= Position[A] && Position[A] <= 0.0) && (Position[A] < BBoxInnerScaledMin[A]))
|
||||
{
|
||||
ScaleDownCenter[A] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool bScaleCenter[3] = {};
|
||||
bScaleCenter[0] = ScaleCenterMode & static_cast<uint8>(EMRUKScaleCenterMode::XAxis) ? true : false;
|
||||
bScaleCenter[1] = ScaleCenterMode & static_cast<uint8>(EMRUKScaleCenterMode::YAxis) ? true : false;
|
||||
bScaleCenter[2] = ScaleCenterMode & static_cast<uint8>(EMRUKScaleCenterMode::ZAxis) ? true : false;
|
||||
|
||||
for (FVector& Position : Positions)
|
||||
{
|
||||
// Apply computations on each axis
|
||||
|
||||
for (int32 A = 0; A < 3; ++A)
|
||||
{
|
||||
if ((bScaleCenter[A] || ScaleDownCenter[A]) && (0.0 <= Position[A] && Position[A] <= BorderPosLS[A]))
|
||||
{
|
||||
// Vertex is inside the inner distance and should be stretched
|
||||
Position[A] *= InnerBoxScaleRatioMax[A];
|
||||
}
|
||||
else if ((bScaleCenter[A] || ScaleDownCenter[A]) && (BorderNegLS[A] <= Position[A] && Position[A] <= 0.0))
|
||||
{
|
||||
// Vertex is inside the inner distance and should be stretched
|
||||
Position[A] *= InnerBoxScaleRatioMin[A];
|
||||
}
|
||||
else if (BorderPosLS[A] < Position[A])
|
||||
{
|
||||
// Vertex is inside the outer stub and should not be stretched
|
||||
// Perform linear transform of vertices into their expect position
|
||||
Position[A] = BorderPosLS[A] * InnerBoxScaleRatioMax[A] + (Position[A] - BorderPosLS[A]);
|
||||
if (BBoxInnerScaledMax[A] < 0.0)
|
||||
{
|
||||
// The mesh that would result from the linear transform above is still not small enough to
|
||||
// fit into the expected scaled down bounding box. This means the stubs need to be scaled down
|
||||
// to make them fit.
|
||||
Position[A] *= DownscaleMax[A];
|
||||
}
|
||||
}
|
||||
else if (Position[A] < BorderNegLS[A])
|
||||
{
|
||||
// Vertex is inside the outer stub and should not be stretched
|
||||
// Perform linear transform of vertices into their expect position
|
||||
Position[A] = BorderNegLS[A] * InnerBoxScaleRatioMin[A] - (BorderNegLS[A] - Position[A]);
|
||||
if (BBoxInnerScaledMin[A] > 0.0)
|
||||
{
|
||||
// The mesh that would result from the linear transform above is still not small enough to
|
||||
// fit into the expected scaled down bounding box. This means the stubs need to be scaled down
|
||||
// to make them fit.
|
||||
Position[A] *= -DownscaleMin[A];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Undo pivot offset
|
||||
Position = ActorScaleInv * ScaledInvPivotTransform.TransformPosition(Position);
|
||||
}
|
||||
|
||||
Triangles.SetNum(IndexBuffer.GetNumIndices());
|
||||
for (int32 I = 0; I < IndexBuffer.GetNumIndices(); ++I)
|
||||
{
|
||||
Triangles[I] = IndexBuffer.GetIndex(I);
|
||||
}
|
||||
|
||||
ProcMesh->ClearMeshSection(0);
|
||||
ProcMesh->CreateMeshSection(0, Positions, Triangles, Normals, UVs, Colors, {}, bGenerateCollision);
|
||||
ProcMesh->SetMaterial(0, Mesh->GetMaterial(0));
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitGuardian.h"
|
||||
|
||||
AMRUKGuardian::AMRUKGuardian(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
// Create a scene component as root so we can attach spawned actors to it
|
||||
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("SceneComponent"));
|
||||
}
|
||||
|
||||
void AMRUKGuardian::CreateGuardian(UProceduralMeshComponent* GuardianMesh)
|
||||
{
|
||||
GuardianMesh->SetupAttachment(RootComponent);
|
||||
GuardianMesh->RegisterComponent();
|
||||
GuardianMeshComponent = GuardianMesh;
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitGuardianSpawner.h"
|
||||
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/GameEngine.h"
|
||||
#include "IXRTrackingSystem.h"
|
||||
#include "Materials/MaterialInstanceDynamic.h"
|
||||
#include "MRUtilityKitAnchor.h"
|
||||
#include "MRUtilityKitGuardian.h"
|
||||
#include "MRUtilityKitRoom.h"
|
||||
#include "MRUtilityKitSubsystem.h"
|
||||
#include "MRUtilityKitTelemetry.h"
|
||||
|
||||
AMRUKGuardianSpawner::AMRUKGuardianSpawner()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
PrimaryActorTick.bTickEvenWhenPaused = true;
|
||||
PrimaryActorTick.TickGroup = TG_PrePhysics;
|
||||
}
|
||||
|
||||
void AMRUKGuardianSpawner::SetGuardianMaterial(UMaterialInstance* Material)
|
||||
{
|
||||
if (!Material)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GuardianMaterial = Material;
|
||||
DynamicGuardianMaterial = UMaterialInstanceDynamic::Create(GuardianMaterial, this);
|
||||
DynamicGuardianMaterial->SetVectorParameterValue(TEXT("WallScale"), FVector(GridDensity));
|
||||
|
||||
// Recreate guardian meshes
|
||||
TArray<AMRUKRoom*> Rooms;
|
||||
SpawnedGuardians.GetKeys(Rooms);
|
||||
for (AMRUKRoom* Room : Rooms)
|
||||
{
|
||||
SpawnGuardians(Room);
|
||||
}
|
||||
}
|
||||
|
||||
void AMRUKGuardianSpawner::SpawnGuardians(AMRUKRoom* Room)
|
||||
{
|
||||
if (!IsValid(Room))
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Can not spawn Guardians for a room that is a nullptr"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove guardians that are already in this room
|
||||
DestroyGuardians(Room);
|
||||
|
||||
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
Subsystem->OnRoomUpdated.AddUniqueDynamic(this, &AMRUKGuardianSpawner::OnRoomUpdated);
|
||||
Subsystem->OnRoomRemoved.AddUniqueDynamic(this, &AMRUKGuardianSpawner::OnRoomRemoved);
|
||||
|
||||
const auto SpawnGuardian = [this](AMRUKAnchor* Anchor, const TArray<FMRUKPlaneUV>& PlaneUVAdjustments) {
|
||||
// Create guardian actor
|
||||
const auto GuardianActor = GetWorld()->SpawnActor<AMRUKGuardian>();
|
||||
GuardianActor->AttachToComponent(Anchor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
|
||||
GuardianActor->SetActorHiddenInGame(IsHidden());
|
||||
|
||||
// Generate procedural mesh
|
||||
const auto ProceduralMesh = NewObject<UProceduralMeshComponent>(GuardianActor, TEXT("GuardianMesh"));
|
||||
Anchor->GenerateProceduralAnchorMesh(ProceduralMesh, PlaneUVAdjustments, {}, true, false, 0.01);
|
||||
ProceduralMesh->SetMaterial(0, DynamicGuardianMaterial);
|
||||
GuardianActor->CreateGuardian(ProceduralMesh);
|
||||
|
||||
return GuardianActor;
|
||||
};
|
||||
|
||||
TArray<AMRUKGuardian*> SpawnedActors;
|
||||
|
||||
// Attach procedural meshes to the walls first because they are connected.
|
||||
TArray<FMRUKAnchorWithPlaneUVs> AnchorsWithPlaneUVs;
|
||||
const TArray<FMRUKTexCoordModes> WallTextureCoordinateModes = { { EMRUKCoordModeU::Metric, EMRUKCoordModeV::Metric } };
|
||||
Room->ComputeWallMeshUVAdjustments(WallTextureCoordinateModes, AnchorsWithPlaneUVs);
|
||||
|
||||
for (const auto& [Anchor, PlaneUVs] : AnchorsWithPlaneUVs)
|
||||
{
|
||||
SpawnedActors.Push(SpawnGuardian(Anchor, PlaneUVs));
|
||||
}
|
||||
|
||||
// Attach procedural meshes to the rest of the anchors. The walls have already meshes applied
|
||||
// because of the first step and will therefore be ignored by this code automatically.
|
||||
for (const auto& Anchor : Room->AllAnchors)
|
||||
{
|
||||
if (!Anchor || Anchor == Room->FloorAnchor || Anchor == Room->CeilingAnchor || Room->IsWallAnchor(Anchor))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
SpawnedActors.Push(SpawnGuardian(Anchor, {}));
|
||||
}
|
||||
|
||||
SpawnedGuardians.Add(Room, SpawnedActors);
|
||||
}
|
||||
|
||||
void AMRUKGuardianSpawner::SetGridDensity(double Density)
|
||||
{
|
||||
GridDensity = Density;
|
||||
|
||||
if (DynamicGuardianMaterial)
|
||||
{
|
||||
DynamicGuardianMaterial->SetVectorParameterValue(TEXT("WallScale"), FVector(GridDensity));
|
||||
}
|
||||
}
|
||||
|
||||
void AMRUKGuardianSpawner::Tick(float DeltaSeconds)
|
||||
{
|
||||
if (!DynamicGuardianMaterial)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (EnableFade)
|
||||
{
|
||||
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
const auto CurrentRoom = Subsystem->GetCurrentRoom();
|
||||
if (!CurrentRoom)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FQuat HeadsetOrientation;
|
||||
FVector HeadsetPosition(0.f);
|
||||
GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, HeadsetOrientation, HeadsetPosition);
|
||||
|
||||
FVector SurfacePosition = FVector::ZeroVector;
|
||||
double SurfaceDistance = 0.0;
|
||||
FMRUKLabelFilter LabelFilter;
|
||||
LabelFilter.ExcludedLabels = { FMRUKLabels::Ceiling, FMRUKLabels::Floor };
|
||||
CurrentRoom->TryGetClosestSurfacePosition(HeadsetPosition, SurfacePosition, SurfaceDistance, LabelFilter);
|
||||
|
||||
const auto WorldToMeters = GetWorldSettings()->WorldToMeters;
|
||||
const auto GuardianFade = FMath::Clamp(1.0 - ((SurfaceDistance / WorldToMeters) / GuardianDistance), 0.0, 1.0);
|
||||
DynamicGuardianMaterial->SetScalarParameterValue(TEXT("Fade"), GuardianFade);
|
||||
}
|
||||
}
|
||||
|
||||
void AMRUKGuardianSpawner::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
SetGuardianMaterial(GuardianMaterial);
|
||||
|
||||
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadGuardianMarker> Event(static_cast<int>(GetTypeHash(this)));
|
||||
|
||||
if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly)
|
||||
{
|
||||
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
if (Subsystem->SceneLoadStatus == EMRUKInitStatus::Complete)
|
||||
{
|
||||
if (AMRUKRoom* CurrentRoom = Subsystem->GetCurrentRoom())
|
||||
{
|
||||
SpawnGuardians(CurrentRoom);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only listen for the room created event in case no current room was available yet
|
||||
Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKGuardianSpawner::OnRoomCreated);
|
||||
}
|
||||
}
|
||||
else if (SpawnMode == EMRUKSpawnMode::AllRooms)
|
||||
{
|
||||
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
for (auto Room : Subsystem->Rooms)
|
||||
{
|
||||
SpawnGuardians(Room);
|
||||
}
|
||||
|
||||
// Listen for new rooms that get created
|
||||
Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKGuardianSpawner::OnRoomCreated);
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
void AMRUKGuardianSpawner::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
||||
{
|
||||
const auto PropertyName = (PropertyChangedEvent.Property != nullptr) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
|
||||
if (PropertyName == GET_MEMBER_NAME_CHECKED(AMRUKGuardianSpawner, GridDensity))
|
||||
{
|
||||
SetGridDensity(GridDensity);
|
||||
}
|
||||
else if (PropertyName == GET_MEMBER_NAME_CHECKED(AMRUKGuardianSpawner, GuardianMaterial))
|
||||
{
|
||||
SetGuardianMaterial(GuardianMaterial);
|
||||
}
|
||||
Super::PostEditChangeProperty(PropertyChangedEvent);
|
||||
}
|
||||
#endif
|
||||
|
||||
void AMRUKGuardianSpawner::OnRoomCreated(AMRUKRoom* Room)
|
||||
{
|
||||
if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly && GetGameInstance()->GetSubsystem<UMRUKSubsystem>()->GetCurrentRoom() != Room)
|
||||
{
|
||||
// Skip this room if it is not the current room
|
||||
return;
|
||||
}
|
||||
|
||||
SpawnGuardians(Room);
|
||||
}
|
||||
|
||||
void AMRUKGuardianSpawner::OnRoomUpdated(AMRUKRoom* Room)
|
||||
{
|
||||
if (!SpawnedGuardians.Find(Room))
|
||||
{
|
||||
// A room was updated that we don't care about. If we are in current room only mode
|
||||
// we only want to update the one room we created
|
||||
return;
|
||||
}
|
||||
SpawnGuardians(Room);
|
||||
}
|
||||
|
||||
void AMRUKGuardianSpawner::OnRoomRemoved(AMRUKRoom* Room)
|
||||
{
|
||||
DestroyGuardians(Room);
|
||||
}
|
||||
|
||||
void AMRUKGuardianSpawner::DestroyGuardians(AMRUKRoom* Room)
|
||||
{
|
||||
if (TArray<AMRUKGuardian*>* Actors = SpawnedGuardians.Find(Room))
|
||||
{
|
||||
for (AActor* Actor : *Actors)
|
||||
{
|
||||
if (IsValid(Actor))
|
||||
{
|
||||
Actor->Destroy();
|
||||
}
|
||||
}
|
||||
Actors->Empty();
|
||||
SpawnedGuardians.Remove(Room);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitLightDispatcher.h"
|
||||
#include "MRUtilityKit.h"
|
||||
#include "MRUtilityKitTelemetry.h"
|
||||
#include "Components/PointLightComponent.h"
|
||||
#include "Engine/PointLight.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "Kismet/KismetMaterialLibrary.h"
|
||||
#include "Materials/MaterialParameterCollection.h"
|
||||
#include "Materials/MaterialParameterCollectionInstance.h"
|
||||
#include "UObject/ConstructorHelpers.h"
|
||||
|
||||
AMRUKLightDispatcher::AMRUKLightDispatcher()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
|
||||
const ConstructorHelpers::FObjectFinder<UMaterialParameterCollection> MpcAsset(TEXT("/OculusXR/Materials/MPC_Highlights"));
|
||||
if (MpcAsset.Succeeded())
|
||||
{
|
||||
Collection = MpcAsset.Object;
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogMRUK, Log, TEXT("Light dispatcher couldn't find material parameter collection in /OculusXR/Materials/MPC_Highlights"));
|
||||
}
|
||||
}
|
||||
|
||||
void AMRUKLightDispatcher::Tick(float DeltaSeconds)
|
||||
{
|
||||
Super::Tick(DeltaSeconds);
|
||||
FillParameterCollection();
|
||||
}
|
||||
|
||||
void AMRUKLightDispatcher::FillParameterCollection()
|
||||
{
|
||||
if (!Collection || PointLightComponents.IsEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UMaterialParameterCollectionInstance* Instance = GetWorld()->GetParameterCollectionInstance(Collection);
|
||||
|
||||
for (int i = 0; i < PointLightComponents.Num(); i++)
|
||||
{
|
||||
const UPointLightComponent* Light = PointLightComponents[i];
|
||||
if (!IsValid(Light))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const int Step = i * 3;
|
||||
|
||||
// It's not possible to expand the amount of parameters in collection at runtime,
|
||||
// in case we exceed the count of existing parameters break the loop
|
||||
if (Collection->VectorParameters.Num() < Step + 3)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Prepare parameters
|
||||
FCollectionVectorParameter PositionParam, DataParam, ColorParam;
|
||||
|
||||
PositionParam.ParameterName = FName("PointLightPosition" + FString::FromInt(i));
|
||||
DataParam.ParameterName = FName("PointLightData" + FString::FromInt(i));
|
||||
ColorParam.ParameterName = FName("PointLightColor" + FString::FromInt(i));
|
||||
|
||||
PositionParam.DefaultValue = FLinearColor(Light->GetComponentLocation());
|
||||
DataParam.DefaultValue = FLinearColor(1.f / Light->AttenuationRadius, Light->ComputeLightBrightness(), Light->LightFalloffExponent, Light->bUseInverseSquaredFalloff);
|
||||
ColorParam.DefaultValue = Light->GetLightColor();
|
||||
|
||||
// Fill collection's vector parameters
|
||||
Collection->VectorParameters[Step] = PositionParam;
|
||||
Collection->VectorParameters[Step + 1] = DataParam;
|
||||
Collection->VectorParameters[Step + 2] = ColorParam;
|
||||
}
|
||||
|
||||
// Send count of lights
|
||||
Collection->ScalarParameters[0].DefaultValue = PointLightComponents.Num();
|
||||
UKismetMaterialLibrary::SetScalarParameterValue(GetWorld(), Collection, "TotalLights", PointLightComponents.Num());
|
||||
|
||||
// Update instance
|
||||
Instance->UpdateRenderState(false);
|
||||
}
|
||||
|
||||
void AMRUKLightDispatcher::AddAdditionalPointLightActor(AActor* Actor)
|
||||
{
|
||||
AdditionalActorsToLookForPointLightComponents.AddUnique(Actor);
|
||||
AddPointLightsFromActor(Actor);
|
||||
}
|
||||
|
||||
void AMRUKLightDispatcher::ForceUpdateCollection()
|
||||
{
|
||||
FillPointLights();
|
||||
FillParameterCollection();
|
||||
PointLightComponents.Empty();
|
||||
}
|
||||
|
||||
void AMRUKLightDispatcher::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadLightDispatcherMarker> Event(static_cast<int>(GetTypeHash(this)));
|
||||
FillPointLights();
|
||||
}
|
||||
|
||||
void AMRUKLightDispatcher::FillPointLights()
|
||||
{
|
||||
// Make sure we don't have duplicates in the array
|
||||
PointLightComponents.Empty();
|
||||
|
||||
if (ShouldFetchPointLightsAtBeginPlay)
|
||||
{
|
||||
// Fetch all point light actors from the level
|
||||
|
||||
TArray<AActor*> PointLightActors;
|
||||
UGameplayStatics::GetAllActorsOfClass(this, APointLight::StaticClass(), PointLightActors);
|
||||
|
||||
for (AActor* Actor : PointLightActors)
|
||||
{
|
||||
const APointLight* PointLightActor = Cast<APointLight>(Actor);
|
||||
|
||||
PointLightComponents.Add(PointLightActor->PointLightComponent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only use the point lights that have been specified in ManualPointLights
|
||||
|
||||
for (AActor* Actor : ManualPointLights)
|
||||
{
|
||||
if (!IsValid(Actor))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const APointLight* PointLightActor = Cast<APointLight>(Actor);
|
||||
|
||||
PointLightComponents.Add(PointLightActor->PointLightComponent);
|
||||
}
|
||||
}
|
||||
|
||||
// Check the additional added actors for point lights and add them in case they have
|
||||
// PointLightComponents attached
|
||||
for (const AActor* Actor : AdditionalActorsToLookForPointLightComponents)
|
||||
{
|
||||
if (!IsValid(Actor))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AddPointLightsFromActor(Actor);
|
||||
}
|
||||
}
|
||||
|
||||
void AMRUKLightDispatcher::AddPointLightsFromActor(const AActor* Actor)
|
||||
{
|
||||
TArray<UPointLightComponent*> LightComponents;
|
||||
Actor->GetComponents(LightComponents, false);
|
||||
PointLightComponents.Append(LightComponents);
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitPositionGenerator.h"
|
||||
#include "MRUtilityKitSubsystem.h"
|
||||
#include "Engine/OverlapResult.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "CollisionShape.h"
|
||||
|
||||
bool AMRUtilityKitPositionGenerator::CanSpawnBox(const UWorld* World, const FBox& Box, const FVector& SpawnPosition, const FQuat& SpawnRotation, const FCollisionQueryParams& QueryParams, const ECollisionChannel CollisionChannel)
|
||||
{
|
||||
TArray<FOverlapResult> OutOverlaps;
|
||||
const bool bHasOverlap = World->OverlapMultiByChannel(OutOverlaps, SpawnPosition, SpawnRotation, CollisionChannel, FCollisionShape::MakeBox(Box.GetExtent()), QueryParams);
|
||||
return !bHasOverlap;
|
||||
}
|
||||
|
||||
void AMRUtilityKitPositionGenerator::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
if (RunOnStart)
|
||||
{
|
||||
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
if (Subsystem->SceneLoadStatus == EMRUKInitStatus::Complete)
|
||||
{
|
||||
SceneLoaded(true);
|
||||
}
|
||||
Subsystem->OnSceneLoaded.AddUniqueDynamic(this, &AMRUtilityKitPositionGenerator::SceneLoaded);
|
||||
}
|
||||
}
|
||||
|
||||
bool AMRUtilityKitPositionGenerator::GenerateRandomPositionsOnSurface(TArray<FTransform>& OutTransforms)
|
||||
{
|
||||
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
bool bSuccess = true;
|
||||
bool bAnyFailure = false;
|
||||
switch (RandomSpawnSettings.RoomFilter)
|
||||
{
|
||||
case EMRUKRoomFilter::None:
|
||||
break;
|
||||
case EMRUKRoomFilter::CurrentRoomOnly:
|
||||
{
|
||||
const auto Room = Subsystem->GetCurrentRoom();
|
||||
bSuccess = GenerateRandomPositionsOnSurfaceInRoom(Room, OutTransforms);
|
||||
break;
|
||||
}
|
||||
case EMRUKRoomFilter::AllRooms:
|
||||
{
|
||||
for (auto& Room : Subsystem->Rooms)
|
||||
{
|
||||
if (!GenerateRandomPositionsOnSurfaceInRoom(Room, OutTransforms))
|
||||
{
|
||||
bAnyFailure = true;
|
||||
}
|
||||
}
|
||||
bSuccess = !bAnyFailure;
|
||||
break;
|
||||
}
|
||||
default:;
|
||||
}
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
bool AMRUtilityKitPositionGenerator::GenerateRandomPositionsOnSurfaceInRoom(AMRUKRoom* Room, TArray<FTransform>& OutTransforms)
|
||||
{
|
||||
bool bInitializedAnchor = IsValid(RandomSpawnSettings.ActorInstance);
|
||||
if (bInitializedAnchor && RandomSpawnSettings.ActorClass != nullptr)
|
||||
{
|
||||
UE_LOG(LogMRUK, Error, TEXT("Cannot use an initialized Actor AND a defined ActorClass together. Use one of the options"));
|
||||
return false;
|
||||
}
|
||||
if (!bInitializedAnchor && RandomSpawnSettings.ActorClass == nullptr)
|
||||
{
|
||||
UE_LOG(LogMRUK, Error, TEXT("Please define ActorClass."));
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
||||
auto Bounds = bInitializedAnchor ? RandomSpawnSettings.ActorInstance->CalculateComponentsBoundingBoxInLocalSpace() : Subsystem->GetActorClassBounds(RandomSpawnSettings.ActorClass);
|
||||
|
||||
float MinRadius = 0.0f;
|
||||
float CenterOffset = (Bounds.GetCenter().Z != 0) ? Bounds.GetCenter().Z : 0.0f;
|
||||
float BaseOffset = (Bounds.Min.Z != 0) ? -Bounds.Min.Z : 0.0f;
|
||||
|
||||
FBox AdjustedBounds;
|
||||
|
||||
TArray<FBox> SpawnedBounds;
|
||||
|
||||
if (Bounds.IsValid)
|
||||
{
|
||||
constexpr float ClearanceDistance = 0.01f;
|
||||
CenterOffset = Bounds.GetCenter().Z;
|
||||
|
||||
MinRadius = FMath::Min(FMath::Min(-Bounds.Min.X, -Bounds.Min.Y), FMath::Min(Bounds.Max.X, Bounds.Max.Y));
|
||||
if (MinRadius < 0.0f)
|
||||
{
|
||||
MinRadius = 0.0f;
|
||||
}
|
||||
|
||||
FVector Min = Bounds.Min;
|
||||
FVector Max = Bounds.Max;
|
||||
Min.Z += ClearanceDistance;
|
||||
|
||||
if (Max.Z < Min.Z)
|
||||
{
|
||||
Max.Z = Min.Z;
|
||||
}
|
||||
|
||||
AdjustedBounds = FBox(Min, Max);
|
||||
if (RandomSpawnSettings.OverrideBounds > 0)
|
||||
{
|
||||
FVector Center = FVector(0.0f, 0.0f, ClearanceDistance);
|
||||
FVector Extents = FVector((RandomSpawnSettings.OverrideBounds), (RandomSpawnSettings.OverrideBounds), ClearanceDistance);
|
||||
AdjustedBounds = FBox(Center - Extents, Center + Extents);
|
||||
}
|
||||
}
|
||||
|
||||
int FoundPositions = 0;
|
||||
|
||||
for (int i = 0; i < RandomSpawnSettings.SpawnAmount; ++i)
|
||||
{
|
||||
for (int j = 0; j < RandomSpawnSettings.MaxIterations; ++j)
|
||||
{
|
||||
FVector SpawnPosition = FVector::ZeroVector;
|
||||
FVector SpawnNormal = FVector::ZeroVector;
|
||||
bool FoundSpawnPos = false;
|
||||
if (RandomSpawnSettings.SpawnLocations == EMRUKSpawnLocation::Floating)
|
||||
{
|
||||
FVector OutPos;
|
||||
if (auto bRandomPos = Room->GenerateRandomPositionInRoom(OutPos, MinRadius, true); !bRandomPos)
|
||||
{
|
||||
break;
|
||||
}
|
||||
SpawnPosition = OutPos;
|
||||
FoundSpawnPos = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FVector Normal, Pos; Room->GenerateRandomPositionOnSurface(RandomSpawnSettings.SpawnLocations, MinRadius, RandomSpawnSettings.Labels, Pos, Normal))
|
||||
{
|
||||
SpawnPosition = Pos + Normal * BaseOffset;
|
||||
SpawnNormal = Normal;
|
||||
auto Center = SpawnPosition + Normal * CenterOffset;
|
||||
if (auto bInRoom = Room->IsPositionInRoom(Center); !bInRoom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (Room->IsPositionInSceneVolume(Center))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (FMRUKHit Hit{}; Room->Raycast(SpawnPosition, Normal, RandomSpawnSettings.SurfaceClearanceDistance, RandomSpawnSettings.Labels, Hit))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
FoundSpawnPos = true;
|
||||
}
|
||||
}
|
||||
FQuat SpawnRotation = FQuat::Identity;
|
||||
if (!SpawnNormal.IsNearlyZero())
|
||||
{
|
||||
SpawnNormal.Normalize();
|
||||
SpawnRotation = FQuat::FindBetweenNormals(FVector::UpVector, SpawnNormal);
|
||||
}
|
||||
|
||||
if (RandomSpawnSettings.CheckOverlaps && Bounds.IsValid && FoundSpawnPos)
|
||||
{
|
||||
FBox WorldBounds(AdjustedBounds.Min + SpawnPosition - AdjustedBounds.GetCenter(), AdjustedBounds.Max + SpawnPosition - AdjustedBounds.GetCenter());
|
||||
|
||||
FVector AdjustedSpawnPos = SpawnPosition + SpawnRotation * AdjustedBounds.GetCenter();
|
||||
|
||||
// check against world
|
||||
if (!CanSpawnBox(GetTickableGameObjectWorld(), WorldBounds, AdjustedSpawnPos, SpawnRotation, FCollisionQueryParams::DefaultQueryParam, RandomSpawnSettings.CollisionChannel))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (bInitializedAnchor && FoundSpawnPos)
|
||||
{
|
||||
RandomSpawnSettings.ActorInstance->SetActorLocationAndRotation(SpawnPosition, SpawnRotation);
|
||||
|
||||
// ignore SpawnAmount once we have a successful move of existing object in the scene
|
||||
return true;
|
||||
}
|
||||
|
||||
if (FoundSpawnPos)
|
||||
{
|
||||
OutTransforms.Add(FTransform(SpawnRotation, SpawnPosition, FVector::OneVector));
|
||||
FoundPositions++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return FoundPositions == RandomSpawnSettings.SpawnAmount;
|
||||
}
|
||||
|
||||
void AMRUtilityKitPositionGenerator::SceneLoaded(bool Success)
|
||||
{
|
||||
if (Success)
|
||||
{
|
||||
TArray<FTransform> OutTransforms;
|
||||
const bool bSuccess = GenerateRandomPositionsOnSurface(OutTransforms);
|
||||
|
||||
if (!bSuccess)
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Generate Random Positions on Surface not successful"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (RandomSpawnSettings.ActorClass != nullptr)
|
||||
{
|
||||
for (auto Transform : OutTransforms)
|
||||
{
|
||||
FActorSpawnParameters Params{};
|
||||
Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||||
GetWorld()->SpawnActor(RandomSpawnSettings.ActorClass, &Transform, Params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitSceneDataProvider.h"
|
||||
#include "UObject/ConstructorHelpers.h"
|
||||
#include "MRUtilityKit.h"
|
||||
|
||||
void AMRUKSceneDataProvider::GetRoom(FString& RoomJSON, FString& RoomName)
|
||||
{
|
||||
if (!bUseRandomRoom)
|
||||
{
|
||||
if (!SpecificRoomName.IsEmpty())
|
||||
{
|
||||
for (const auto& Room : Rooms)
|
||||
{
|
||||
const auto RoomDT = Room.Value;
|
||||
const auto TmpJSON = RoomDT->FindRow<FJSONData>(FName(SpecificRoomName), "", false);
|
||||
if (TmpJSON != nullptr)
|
||||
{
|
||||
RoomJSON = TmpJSON->JSON;
|
||||
RoomName = SpecificRoomName;
|
||||
return;
|
||||
}
|
||||
}
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Specific room name not found, using random room."));
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Specific room name not defined, using random room."));
|
||||
}
|
||||
}
|
||||
|
||||
if (bUseRandomRoomFromClass)
|
||||
{
|
||||
if (!SpecificRoomClass.IsEmpty())
|
||||
{
|
||||
const auto RoomDT = *Rooms.Find(SpecificRoomClass);
|
||||
if (RoomDT != nullptr)
|
||||
{
|
||||
TArray<FJSONData*> TmpArray;
|
||||
RoomDT->GetAllRows("", TmpArray);
|
||||
auto TmpRowNames = RoomDT->GetRowNames();
|
||||
const auto Num = TmpArray.Num() - 1;
|
||||
const auto Idx = FMath::RandRange(0, Num);
|
||||
|
||||
RoomJSON = TmpArray[Idx]->JSON;
|
||||
RoomName = TmpRowNames[Idx].ToString();
|
||||
return;
|
||||
}
|
||||
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Specific room class not found, using random room."));
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Specific room class not defined, using random room."));
|
||||
}
|
||||
}
|
||||
|
||||
auto Num = Rooms.Num() - 1;
|
||||
auto Idx = FMath::RandRange(0, Num);
|
||||
|
||||
TArray<UDataTable*> ChildArray;
|
||||
Rooms.GenerateValueArray(ChildArray);
|
||||
|
||||
const auto Room = ChildArray[Idx];
|
||||
|
||||
Num = Room->GetRowMap().Num() - 1;
|
||||
Idx = FMath::RandRange(0, Num);
|
||||
|
||||
TArray<FJSONData*> RandomRoomRows;
|
||||
auto RandomRoomRowNames = Room->GetRowNames();
|
||||
Room->GetAllRows("", RandomRoomRows);
|
||||
|
||||
RoomJSON = RandomRoomRows[Idx]->JSON;
|
||||
RoomName = RandomRoomRowNames[Idx].ToString();
|
||||
}
|
||||
|
||||
// Called when the game starts or when spawned
|
||||
void AMRUKSceneDataProvider::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
// Called every frame
|
||||
void AMRUKSceneDataProvider::Tick(float DeltaTime)
|
||||
{
|
||||
Super::Tick(DeltaTime);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitSeatsComponent.h"
|
||||
#include "MRUtilityKitAnchor.h"
|
||||
#include "MRUtilityKitRoom.h"
|
||||
#include "Kismet/KismetMathLibrary.h"
|
||||
|
||||
void UMRUKSeatsComponent::CalculateSeatPoses(double SeatWidth)
|
||||
{
|
||||
const auto Anchor = Cast<AMRUKAnchor>(GetOwner());
|
||||
if (!Anchor)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SeatPoses.Empty();
|
||||
|
||||
const auto SurfaceDimensions = Anchor->PlaneBounds.GetExtent();
|
||||
const auto SurfaceRatio = SurfaceDimensions.X / SurfaceDimensions.Y;
|
||||
const auto SeatForward = Anchor->GetFacingDirection();
|
||||
const auto SeatUp = FVector::UpVector;
|
||||
const auto SeatRotation = UKismetMathLibrary::MakeRotFromXZ(SeatForward, SeatUp).Quaternion();
|
||||
|
||||
if (SurfaceRatio < 2.0 && SurfaceRatio > 0.5)
|
||||
{
|
||||
// If the surface dimensions are mostly square (likely a chair), just have one centered seat.
|
||||
FTransform SeatPose{};
|
||||
SeatPose.SetLocation(Anchor->GetActorLocation());
|
||||
SeatPose.SetRotation(SeatRotation);
|
||||
SeatPoses.Add(SeatPose);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto XLong = SurfaceDimensions.X > SurfaceDimensions.Y;
|
||||
const auto LongestDimension = XLong ? SurfaceDimensions.X : SurfaceDimensions.Y;
|
||||
const auto NumSeats = FMath::Floor(LongestDimension / SeatWidth);
|
||||
|
||||
const auto SeatPadding = (LongestDimension - (NumSeats * SeatWidth)) / NumSeats;
|
||||
const auto FirstSeatOffset = (-LongestDimension + SeatPadding + SeatWidth) * 0.5;
|
||||
|
||||
for (int i = 0; i < NumSeats; ++i)
|
||||
{
|
||||
const auto SeatRight = XLong ? Anchor->GetActorRightVector() : Anchor->GetActorUpVector();
|
||||
|
||||
const auto Offset = FirstSeatOffset + (SeatWidth + SeatPadding) * i;
|
||||
const auto SeatPosition = Anchor->GetActorLocation() + SeatRight * Offset;
|
||||
|
||||
FTransform SeatPose{};
|
||||
SeatPose.SetLocation(SeatPosition);
|
||||
SeatPose.SetRotation(SeatRotation);
|
||||
SeatPoses.Add(SeatPose);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitSerializationHelpers.h"
|
||||
|
||||
TSharedPtr<FJsonValue> MRUKSerialize(const FString& String)
|
||||
{
|
||||
return MakeShareable(new FJsonValueString(String));
|
||||
}
|
||||
|
||||
void MRUKDeserialize(const FJsonValue& Value, FString& String)
|
||||
{
|
||||
String = Value.AsString();
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonValue> MRUKSerialize(const FOculusXRUUID& UUID)
|
||||
{
|
||||
return MakeShareable(new FJsonValueString(UUID.ToString()));
|
||||
}
|
||||
|
||||
void MRUKDeserialize(const FJsonValue& Value, FOculusXRUUID& UUID)
|
||||
{
|
||||
const auto Hex = Value.AsString();
|
||||
if (Hex.Len() == OCULUSXR_UUID_SIZE * 2)
|
||||
{
|
||||
HexToBytes(Hex, UUID.UUIDBytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogJson, Error, TEXT("Json String '%s' is not of expected length %d when deserializing FOculusXRUUID"), *Hex, OCULUSXR_UUID_SIZE * 2);
|
||||
UUID = FOculusXRUUID();
|
||||
}
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonValue> MRUKSerialize(const double& Number)
|
||||
{
|
||||
return MakeShareable(new FJsonValueNumber(Number));
|
||||
}
|
||||
|
||||
void MRUKDeserialize(const FJsonValue& Value, double& Number)
|
||||
{
|
||||
Number = Value.AsNumber();
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonValue> MRUKSerialize(const FOculusXRRoomLayout& RoomLayout)
|
||||
{
|
||||
const TSharedRef<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
|
||||
// Note: No need to serialize the list of room object UUIDs since it is just the list of
|
||||
// all anchors in the room
|
||||
JsonObject->SetField(TEXT("FloorUuid"), MRUKSerialize(RoomLayout.FloorUuid));
|
||||
JsonObject->SetField(TEXT("CeilingUuid"), MRUKSerialize(RoomLayout.CeilingUuid));
|
||||
JsonObject->SetField(TEXT("WallsUuid"), MRUKSerialize(RoomLayout.WallsUuid));
|
||||
return MakeShareable(new FJsonValueObject(JsonObject));
|
||||
}
|
||||
|
||||
void MRUKDeserialize(const FJsonValue& Value, FOculusXRRoomLayout& RoomLayout)
|
||||
{
|
||||
const auto Object = Value.AsObject();
|
||||
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("FloorUuid")), RoomLayout.FloorUuid);
|
||||
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("CeilingUuid")), RoomLayout.CeilingUuid);
|
||||
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("WallsUuid")), RoomLayout.WallsUuid);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "Generated/MRUtilityKitShared.h"
|
||||
#include "MRUtilityKit.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "HAL/PlatformProcess.h"
|
||||
#include "Interfaces/IPluginManager.h"
|
||||
|
||||
MRUKShared* MRUKShared::Instance;
|
||||
|
||||
MRUKShared::MRUKShared(void* handle)
|
||||
: MRUKSharedHandle(handle)
|
||||
{
|
||||
LoadNativeFunctions();
|
||||
}
|
||||
|
||||
MRUKShared::~MRUKShared()
|
||||
{
|
||||
UnloadNativeFunctions();
|
||||
|
||||
FPlatformProcess::FreeDllHandle(MRUKSharedHandle);
|
||||
MRUKSharedHandle = nullptr;
|
||||
}
|
||||
|
||||
void MRUKShared::LoadMRUKSharedLibrary()
|
||||
{
|
||||
if (Instance != nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Load
|
||||
UE_LOG(LogMRUK, Log, TEXT("Loading MR Utility Kit Shared library"));
|
||||
#if PLATFORM_WINDOWS
|
||||
const FString BinariesPath = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("OculusXR"))->GetBaseDir(), TEXT("/Source/Thirdparty/MRUtilityKitShared/Lib/Win64"));
|
||||
FPlatformProcess::PushDllDirectory(*BinariesPath);
|
||||
void* handle = FPlatformProcess::GetDllHandle(TEXT("mrutilitykitshared.dll"));
|
||||
FPlatformProcess::PopDllDirectory(*BinariesPath);
|
||||
#elif PLATFORM_ANDROID
|
||||
void* handle = FPlatformProcess::GetDllHandle(TEXT("libmrutilitykitshared.so"));
|
||||
#endif // PLATFORM_ANDROID
|
||||
|
||||
if (handle == nullptr)
|
||||
{
|
||||
UE_LOG(LogMRUK, Error, TEXT("Failed to load MR Utility Kit Shared library"));
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = new MRUKShared(handle);
|
||||
}
|
||||
|
||||
void MRUKShared::FreeMRUKSharedLibrary()
|
||||
{
|
||||
if (Instance == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
delete Instance;
|
||||
Instance = nullptr;
|
||||
}
|
||||
|
||||
void* MRUKShared::LoadFunction(const TCHAR* ProcName)
|
||||
{
|
||||
auto func = FPlatformProcess::GetDllExport(MRUKSharedHandle, ProcName);
|
||||
if (func == nullptr)
|
||||
{
|
||||
UE_LOG(LogMRUK, Error, TEXT("Failed to load native function: %s"), ProcName);
|
||||
}
|
||||
return func;
|
||||
}
|
||||
@@ -0,0 +1,574 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitSubsystem.h"
|
||||
#include "MRUtilityKitAnchor.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "HeadMountedDisplayFunctionLibrary.h"
|
||||
#include "MRUtilityKitPositionGenerator.h"
|
||||
#include "Serialization/JsonWriter.h"
|
||||
#include "Serialization/JsonSerializer.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "OculusXRRoomLayoutManagerComponent.h"
|
||||
#include "OculusXRSceneEventDelegates.h"
|
||||
#include "OculusXRSceneFunctionLibrary.h"
|
||||
#include "Engine/Engine.h"
|
||||
#if WITH_EDITOR
|
||||
#include "Editor.h"
|
||||
#endif // WITH_EDITOR
|
||||
#include "Generated/MRUtilityKitShared.h"
|
||||
|
||||
AMRUKAnchor* UMRUKSubsystem::Raycast(const FVector& Origin, const FVector& Direction, float MaxDist, const FMRUKLabelFilter& LabelFilter, FMRUKHit& OutHit)
|
||||
{
|
||||
AMRUKAnchor* HitComponent = nullptr;
|
||||
for (const auto& Room : Rooms)
|
||||
{
|
||||
FMRUKHit HitResult;
|
||||
if (!Room)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (AMRUKAnchor* Anchor = Room->Raycast(Origin, Direction, MaxDist, LabelFilter, HitResult))
|
||||
{
|
||||
// Prevent further hits which are further away from being found
|
||||
MaxDist = HitResult.HitDistance;
|
||||
OutHit = HitResult;
|
||||
HitComponent = Anchor;
|
||||
}
|
||||
}
|
||||
return HitComponent;
|
||||
}
|
||||
|
||||
bool UMRUKSubsystem::RaycastAll(const FVector& Origin, const FVector& Direction, float MaxDist, const FMRUKLabelFilter& LabelFilter, TArray<FMRUKHit>& OutHits, TArray<AMRUKAnchor*>& OutAnchors)
|
||||
{
|
||||
bool HitAnything = false;
|
||||
for (const auto& Room : Rooms)
|
||||
{
|
||||
if (!Room)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (Room->RaycastAll(Origin, Direction, MaxDist, LabelFilter, OutHits, OutAnchors))
|
||||
{
|
||||
HitAnything = true;
|
||||
}
|
||||
}
|
||||
return HitAnything;
|
||||
}
|
||||
|
||||
void UMRUKSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
const UMRUKSettings* Settings = GetMutableDefault<UMRUKSettings>();
|
||||
EnableWorldLock = Settings->EnableWorldLock;
|
||||
|
||||
MRUKShared::LoadMRUKSharedLibrary();
|
||||
}
|
||||
|
||||
void UMRUKSubsystem::Deinitialize()
|
||||
{
|
||||
MRUKShared::FreeMRUKSharedLibrary();
|
||||
}
|
||||
|
||||
TSharedRef<FJsonObject> UMRUKSubsystem::JsonSerialize()
|
||||
{
|
||||
TSharedRef<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
|
||||
TArray<TSharedPtr<FJsonValue>> RoomsArray;
|
||||
|
||||
for (const auto& Room : Rooms)
|
||||
{
|
||||
if (Room)
|
||||
{
|
||||
RoomsArray.Add(MakeShareable(new FJsonValueObject(Room->JsonSerialize())));
|
||||
}
|
||||
}
|
||||
|
||||
JsonObject->SetArrayField(TEXT("Rooms"), RoomsArray);
|
||||
|
||||
return JsonObject;
|
||||
}
|
||||
|
||||
void UMRUKSubsystem::UnregisterRoom(AMRUKRoom* Room)
|
||||
{
|
||||
Rooms.Remove(Room);
|
||||
}
|
||||
|
||||
AMRUKRoom* UMRUKSubsystem::GetCurrentRoom() const
|
||||
{
|
||||
// This is a rather expensive operation, we should only do it at most once per frame.
|
||||
if (CachedCurrentRoomFrame != GFrameCounter)
|
||||
{
|
||||
if (const APlayerController* PlayerController = UGameplayStatics::GetPlayerController(this, 0))
|
||||
{
|
||||
if (APawn* Pawn = PlayerController->GetPawn())
|
||||
{
|
||||
const auto& PawnTransform = Pawn->GetActorTransform();
|
||||
|
||||
FVector HeadPosition;
|
||||
FRotator Unused;
|
||||
|
||||
// Get the position and rotation of the VR headset
|
||||
UHeadMountedDisplayFunctionLibrary::GetOrientationAndPosition(Unused, HeadPosition);
|
||||
|
||||
HeadPosition = PawnTransform.TransformPosition(HeadPosition);
|
||||
|
||||
for (const auto& Room : Rooms)
|
||||
{
|
||||
if (IsValid(Room) && Room->IsPositionInRoom(HeadPosition))
|
||||
{
|
||||
CachedCurrentRoom = Room;
|
||||
CachedCurrentRoomFrame = GFrameCounter;
|
||||
return Room;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IsValid(CachedCurrentRoom))
|
||||
{
|
||||
return CachedCurrentRoom;
|
||||
}
|
||||
|
||||
for (const auto& Room : Rooms)
|
||||
{
|
||||
if (IsValid(Room))
|
||||
{
|
||||
return Room;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FString UMRUKSubsystem::SaveSceneToJsonString()
|
||||
{
|
||||
FString Json;
|
||||
const TSharedRef<TJsonWriter<>> JsonWriter = TJsonWriterFactory<>::Create(&Json, 0);
|
||||
FJsonSerializer::Serialize(JsonSerialize(), JsonWriter);
|
||||
return Json;
|
||||
}
|
||||
|
||||
void UMRUKSubsystem::LoadSceneFromJsonString(const FString& String)
|
||||
{
|
||||
if (SceneData || SceneLoadStatus == EMRUKInitStatus::Busy)
|
||||
{
|
||||
UE_LOG(LogMRUK, Error, TEXT("Can't start loading a scene from JSON while the scene is already loading"));
|
||||
return;
|
||||
}
|
||||
|
||||
SceneData = NewObject<UMRUKSceneData>(this);
|
||||
|
||||
if (SceneLoadStatus == EMRUKInitStatus::Complete)
|
||||
{
|
||||
// Update the scene
|
||||
UE_LOG(LogMRUK, Log, TEXT("Update scene from JSON"));
|
||||
SceneData->OnComplete.AddDynamic(this, &UMRUKSubsystem::UpdatedSceneDataLoadedComplete);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogMRUK, Log, TEXT("Load scene from JSON"));
|
||||
SceneData->OnComplete.AddDynamic(this, &UMRUKSubsystem::SceneDataLoadedComplete);
|
||||
}
|
||||
SceneLoadStatus = EMRUKInitStatus::Busy;
|
||||
SceneData->LoadFromJson(String);
|
||||
}
|
||||
|
||||
void UMRUKSubsystem::LoadSceneFromDevice()
|
||||
{
|
||||
if (SceneData || SceneLoadStatus == EMRUKInitStatus::Busy)
|
||||
{
|
||||
UE_LOG(LogMRUK, Error, TEXT("Can't start loading a scene from device while the scene is already loading"));
|
||||
if (SceneData)
|
||||
{
|
||||
UE_LOG(LogMRUK, Error, TEXT("Ongoing scene data query"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
SceneData = NewObject<UMRUKSceneData>(this);
|
||||
if (!Rooms.IsEmpty())
|
||||
{
|
||||
// Update the scene
|
||||
UE_LOG(LogMRUK, Log, TEXT("Update scene from device"));
|
||||
SceneData->OnComplete.AddDynamic(this, &UMRUKSubsystem::UpdatedSceneDataLoadedComplete);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogMRUK, Log, TEXT("Load scene from device"));
|
||||
SceneData->OnComplete.AddDynamic(this, &UMRUKSubsystem::SceneDataLoadedComplete);
|
||||
}
|
||||
SceneLoadStatus = EMRUKInitStatus::Busy;
|
||||
#if WITH_EDITOR
|
||||
if (GetWorld()->WorldType == EWorldType::PIE && GEditor->IsSimulateInEditorInProgress())
|
||||
{
|
||||
// LoadFromDevice sometimes doesn't broadcast failure when running in simulate mode. We can skip trying and just fail immediately in this case.
|
||||
SceneData->OnComplete.Broadcast(false);
|
||||
}
|
||||
else
|
||||
#endif // WITH_EDITOR
|
||||
{
|
||||
SceneData->LoadFromDevice();
|
||||
}
|
||||
}
|
||||
|
||||
void UMRUKSubsystem::SceneDataLoadedComplete(bool Success)
|
||||
{
|
||||
UE_LOG(LogMRUK, Log, TEXT("Loaded scene data. Success==%d"), Success);
|
||||
if (!SceneData)
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("Can't process scene data if it's not loaded"));
|
||||
FinishedLoading(false);
|
||||
return;
|
||||
}
|
||||
if (SceneData->RoomsData.IsEmpty())
|
||||
{
|
||||
UE_LOG(LogMRUK, Warning, TEXT("No room data found"));
|
||||
FinishedLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Success)
|
||||
{
|
||||
UE_LOG(LogMRUK, Log, TEXT("Spawn rooms from scene data"));
|
||||
for (const auto& RoomData : SceneData->RoomsData)
|
||||
{
|
||||
AMRUKRoom* Room = SpawnRoom();
|
||||
Room->LoadFromData(RoomData);
|
||||
}
|
||||
}
|
||||
FinishedLoading(Success);
|
||||
|
||||
for (const auto& Room : Rooms)
|
||||
{
|
||||
OnRoomCreated.Broadcast(Room);
|
||||
}
|
||||
}
|
||||
|
||||
void UMRUKSubsystem::UpdatedSceneDataLoadedComplete(bool Success)
|
||||
{
|
||||
UE_LOG(LogMRUK, Log, TEXT("Loaded updated scene data from device. Sucess==%d"), Success);
|
||||
|
||||
TArray<TObjectPtr<AMRUKRoom>> RoomsCreated;
|
||||
TArray<TObjectPtr<AMRUKRoom>> RoomsUpdated;
|
||||
|
||||
if (Success)
|
||||
{
|
||||
UE_LOG(LogMRUK, Log, TEXT("Update found %d rooms"), SceneData->RoomsData.Num());
|
||||
|
||||
TArray<TObjectPtr<AMRUKRoom>> RoomsToRemove = Rooms;
|
||||
Rooms.Empty();
|
||||
|
||||
for (int i = 0; i < SceneData->RoomsData.Num(); ++i)
|
||||
{
|
||||
UMRUKRoomData* RoomData = SceneData->RoomsData[i];
|
||||
const TObjectPtr<AMRUKRoom>* RoomFound = RoomsToRemove.FindByPredicate([RoomData](TObjectPtr<AMRUKRoom> Room) {
|
||||
return Room->Corresponds(RoomData);
|
||||
});
|
||||
TObjectPtr<AMRUKRoom> Room = nullptr;
|
||||
if (RoomFound)
|
||||
{
|
||||
Room = *RoomFound;
|
||||
UE_LOG(LogMRUK, Log, TEXT("Update room from query"));
|
||||
Rooms.Push(Room);
|
||||
RoomsToRemove.Remove(Room);
|
||||
RoomsUpdated.Push(Room);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogMRUK, Log, TEXT("Spawn room from query"));
|
||||
Room = SpawnRoom();
|
||||
RoomsCreated.Push(Room);
|
||||
}
|
||||
Room->LoadFromData(RoomData);
|
||||
}
|
||||
|
||||
UE_LOG(LogMRUK, Log, TEXT("Destroy %d old rooms"), RoomsToRemove.Num());
|
||||
for (const auto& Room : RoomsToRemove)
|
||||
{
|
||||
OnRoomRemoved.Broadcast(Room);
|
||||
Room->Destroy();
|
||||
}
|
||||
}
|
||||
FinishedLoading(Success);
|
||||
|
||||
for (const auto& Room : RoomsUpdated)
|
||||
{
|
||||
OnRoomUpdated.Broadcast(Room);
|
||||
}
|
||||
for (const auto& Room : RoomsCreated)
|
||||
{
|
||||
OnRoomCreated.Broadcast(Room);
|
||||
}
|
||||
}
|
||||
|
||||
void UMRUKSubsystem::ClearScene()
|
||||
{
|
||||
if (SceneLoadStatus == EMRUKInitStatus::Busy)
|
||||
{
|
||||
UE_LOG(LogMRUK, Error, TEXT("Cannot clear scene while scene is loading"));
|
||||
return;
|
||||
}
|
||||
SceneLoadStatus = EMRUKInitStatus::None;
|
||||
// No ranged for loop because rooms may remove themselves from the array during destruction
|
||||
for (int32 I = Rooms.Num() - 1; I >= 0; --I)
|
||||
{
|
||||
AMRUKRoom* Room = Rooms[I];
|
||||
if (IsValid(Room))
|
||||
{
|
||||
Room->Destroy();
|
||||
}
|
||||
}
|
||||
Rooms.Empty();
|
||||
}
|
||||
|
||||
AMRUKAnchor* UMRUKSubsystem::TryGetClosestSurfacePosition(const FVector& WorldPosition, FVector& OutSurfacePosition, const FMRUKLabelFilter& LabelFilter, double MaxDistance)
|
||||
{
|
||||
AMRUKAnchor* ClosestAnchor = nullptr;
|
||||
|
||||
for (const auto& Room : Rooms)
|
||||
{
|
||||
if (!Room)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
double SurfaceDistance{};
|
||||
FVector SurfacePos{};
|
||||
if (const auto Anchor = Room->TryGetClosestSurfacePosition(WorldPosition, SurfacePos, SurfaceDistance, LabelFilter, MaxDistance))
|
||||
{
|
||||
ClosestAnchor = Anchor;
|
||||
OutSurfacePosition = SurfacePos;
|
||||
MaxDistance = SurfaceDistance;
|
||||
}
|
||||
}
|
||||
|
||||
return ClosestAnchor;
|
||||
}
|
||||
|
||||
AMRUKAnchor* UMRUKSubsystem::TryGetClosestSeatPose(const FVector& RayOrigin, const FVector& RayDirection, FTransform& OutSeatTransform)
|
||||
{
|
||||
AMRUKAnchor* ClosestAnchor = nullptr;
|
||||
double ClosestSeatDistanceSq = DBL_MAX;
|
||||
|
||||
for (const auto& Room : Rooms)
|
||||
{
|
||||
if (!Room)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FTransform SeatTransform{};
|
||||
if (AMRUKAnchor* Anchor = Room->TryGetClosestSeatPose(RayOrigin, RayDirection, SeatTransform))
|
||||
{
|
||||
const double SeatDistanceSq = (RayOrigin - Anchor->GetActorTransform().GetTranslation()).SquaredLength();
|
||||
if (SeatDistanceSq < ClosestSeatDistanceSq)
|
||||
{
|
||||
ClosestAnchor = Anchor;
|
||||
ClosestSeatDistanceSq = SeatDistanceSq;
|
||||
OutSeatTransform = SeatTransform;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ClosestAnchor;
|
||||
}
|
||||
|
||||
AMRUKAnchor* UMRUKSubsystem::GetBestPoseFromRaycast(const FVector& RayOrigin, const FVector& RayDirection, double MaxDist, const FMRUKLabelFilter& LabelFilter, FTransform& OutPose, EMRUKPositioningMethod PositioningMethod)
|
||||
{
|
||||
AMRUKAnchor* ClosestAnchor = nullptr;
|
||||
double ClosestPoseDistanceSq = DBL_MAX;
|
||||
|
||||
for (const auto& Room : Rooms)
|
||||
{
|
||||
if (!Room)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FTransform Pose{};
|
||||
AMRUKAnchor* Anchor = Room->GetBestPoseFromRaycast(RayOrigin, RayDirection, MaxDist, LabelFilter, Pose, PositioningMethod);
|
||||
if (Anchor)
|
||||
{
|
||||
const double PoseDistanceSq = (RayOrigin - OutPose.GetTranslation()).SquaredLength();
|
||||
if (PoseDistanceSq < ClosestPoseDistanceSq)
|
||||
{
|
||||
ClosestAnchor = Anchor;
|
||||
ClosestPoseDistanceSq = PoseDistanceSq;
|
||||
OutPose = Pose;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ClosestAnchor;
|
||||
}
|
||||
|
||||
AMRUKAnchor* UMRUKSubsystem::GetKeyWall(double Tolerance)
|
||||
{
|
||||
if (AMRUKRoom* CurrentRoom = GetCurrentRoom())
|
||||
{
|
||||
return CurrentRoom->GetKeyWall(Tolerance);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AMRUKAnchor* UMRUKSubsystem::GetLargestSurface(const FString& Label)
|
||||
{
|
||||
if (AMRUKRoom* CurrentRoom = GetCurrentRoom())
|
||||
{
|
||||
return CurrentRoom->GetLargestSurface(Label);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AMRUKAnchor* UMRUKSubsystem::IsPositionInSceneVolume(const FVector& WorldPosition, bool TestVerticalBounds, double Tolerance)
|
||||
{
|
||||
for (const auto& Room : Rooms)
|
||||
{
|
||||
if (!Room)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (const auto Anchor = Room->IsPositionInSceneVolume(WorldPosition, TestVerticalBounds, Tolerance))
|
||||
{
|
||||
return Anchor;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TArray<AActor*> UMRUKSubsystem::SpawnInterior(const TMap<FString, FMRUKSpawnGroup>& SpawnGroups, const TArray<FString>& CutHoleLabels, UMaterialInterface* ProceduralMaterial, bool ShouldFallbackToProcedural)
|
||||
{
|
||||
return SpawnInteriorFromStream(SpawnGroups, FRandomStream(NAME_None), CutHoleLabels, ProceduralMaterial, ShouldFallbackToProcedural);
|
||||
}
|
||||
|
||||
TArray<AActor*> UMRUKSubsystem::SpawnInteriorFromStream(const TMap<FString, FMRUKSpawnGroup>& SpawnGroups, const FRandomStream& RandomStream, const TArray<FString>& CutHoleLabels, UMaterialInterface* ProceduralMaterial, bool ShouldFallbackToProcedural)
|
||||
{
|
||||
TArray<AActor*> AllInteriorActors;
|
||||
|
||||
for (const auto& Room : Rooms)
|
||||
{
|
||||
if (!Room)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto InteriorActors = Room->SpawnInteriorFromStream(SpawnGroups, RandomStream, CutHoleLabels, ProceduralMaterial, ShouldFallbackToProcedural);
|
||||
AllInteriorActors.Append(InteriorActors);
|
||||
}
|
||||
|
||||
return AllInteriorActors;
|
||||
}
|
||||
|
||||
bool UMRUKSubsystem::LaunchSceneCapture()
|
||||
{
|
||||
const bool Success = GetRoomLayoutManager()->LaunchCaptureFlow();
|
||||
if (Success)
|
||||
{
|
||||
UE_LOG(LogMRUK, Log, TEXT("Capture flow launched with success"));
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogMRUK, Error, TEXT("Launching capture flow failed!"));
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
FBox UMRUKSubsystem::GetActorClassBounds(TSubclassOf<AActor> Actor)
|
||||
{
|
||||
if (const auto Entry = ActorClassBoundsCache.Find(Actor))
|
||||
{
|
||||
return *Entry;
|
||||
}
|
||||
const auto TempActor = GetWorld()->SpawnActor(Actor);
|
||||
const auto Bounds = TempActor->CalculateComponentsBoundingBoxInLocalSpace(true);
|
||||
TempActor->Destroy();
|
||||
ActorClassBoundsCache.Add(Actor, Bounds);
|
||||
return Bounds;
|
||||
}
|
||||
|
||||
void UMRUKSubsystem::SceneCaptureComplete(FOculusXRUInt64 RequestId, bool bSuccess)
|
||||
{
|
||||
UE_LOG(LogMRUK, Log, TEXT("Scene capture complete Success==%d"), bSuccess);
|
||||
OnCaptureComplete.Broadcast(bSuccess);
|
||||
}
|
||||
|
||||
UOculusXRRoomLayoutManagerComponent* UMRUKSubsystem::GetRoomLayoutManager()
|
||||
{
|
||||
if (!RoomLayoutManager)
|
||||
{
|
||||
FActorSpawnParameters Params{};
|
||||
Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||||
Params.Owner = nullptr;
|
||||
RoomLayoutManagerActor = GetWorld()->SpawnActor<AActor>(Params);
|
||||
RoomLayoutManagerActor->SetRootComponent(NewObject<USceneComponent>(RoomLayoutManagerActor, TEXT("SceneComponent")));
|
||||
|
||||
RoomLayoutManagerActor->AddComponentByClass(UOculusXRRoomLayoutManagerComponent::StaticClass(), false, FTransform::Identity, false);
|
||||
RoomLayoutManager = RoomLayoutManagerActor->GetComponentByClass<UOculusXRRoomLayoutManagerComponent>();
|
||||
RoomLayoutManager->OculusXRRoomLayoutSceneCaptureComplete.AddDynamic(this, &UMRUKSubsystem::SceneCaptureComplete);
|
||||
}
|
||||
return RoomLayoutManager;
|
||||
}
|
||||
|
||||
AMRUKRoom* UMRUKSubsystem::SpawnRoom()
|
||||
{
|
||||
FActorSpawnParameters ActorSpawnParams;
|
||||
ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||||
AMRUKRoom* Room = GetWorld()->SpawnActor<AMRUKRoom>(ActorSpawnParams);
|
||||
|
||||
#if WITH_EDITOR
|
||||
Room->SetActorLabel(TEXT("ROOM"));
|
||||
#endif
|
||||
|
||||
Rooms.Push(Room);
|
||||
|
||||
return Room;
|
||||
}
|
||||
|
||||
void UMRUKSubsystem::FinishedLoading(bool Success)
|
||||
{
|
||||
UE_LOG(LogMRUK, Log, TEXT("Finished loading: Success==%d"), Success);
|
||||
if (SceneData)
|
||||
{
|
||||
SceneData->MarkAsGarbage();
|
||||
SceneData = nullptr;
|
||||
}
|
||||
|
||||
if (Success)
|
||||
{
|
||||
SceneLoadStatus = EMRUKInitStatus::Complete;
|
||||
}
|
||||
else
|
||||
{
|
||||
SceneLoadStatus = EMRUKInitStatus::Failed;
|
||||
}
|
||||
OnSceneLoaded.Broadcast(Success);
|
||||
}
|
||||
|
||||
void UMRUKSubsystem::Tick(float DeltaTime)
|
||||
{
|
||||
if (EnableWorldLock)
|
||||
{
|
||||
if (const auto Room = GetCurrentRoom())
|
||||
{
|
||||
if (const APlayerController* PlayerController = UGameplayStatics::GetPlayerController(this, 0))
|
||||
{
|
||||
if (APawn* Pawn = PlayerController->GetPawn())
|
||||
{
|
||||
const auto& PawnTransform = Pawn->GetActorTransform();
|
||||
|
||||
FVector HeadPosition;
|
||||
FRotator Unused;
|
||||
|
||||
// Get the position and rotation of the VR headset
|
||||
UHeadMountedDisplayFunctionLibrary::GetOrientationAndPosition(Unused, HeadPosition);
|
||||
|
||||
HeadPosition = PawnTransform.TransformPosition(HeadPosition);
|
||||
|
||||
Room->UpdateWorldLock(Pawn, HeadPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UMRUKSubsystem::IsTickable() const
|
||||
{
|
||||
return !HasAnyFlags(RF_BeginDestroyed) && IsValidChecked(this) && (EnableWorldLock);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,412 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
|
||||
#include "MRUtilityKit.generated.h"
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogMRUK, Log, All);
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EMRUKInitStatus : uint8
|
||||
{
|
||||
/// Not Initialized.
|
||||
None,
|
||||
/// Is busy Initializing.
|
||||
Busy,
|
||||
/// Has finished Initializing.
|
||||
Complete,
|
||||
/// Failed to initialize.
|
||||
Failed,
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EMRUKCoordModeU : uint8
|
||||
{
|
||||
/// The texture coordinates start at 0 and increase by 1 unit every meter.
|
||||
Metric,
|
||||
/// The texture coordinates start at 0 and increase by 1 unit every meter but are adjusted to end on a whole number to avoid seams.
|
||||
MetricSeamless,
|
||||
/// The texture coordinates are adjusted to the other dimensions to ensure the aspect ratio is maintained.
|
||||
MaintainAspectRatio,
|
||||
/// The texture coordinates are adjusted to the other dimensions to ensure the aspect ratio is maintained but are adjusted to end on a whole number to avoid seams.
|
||||
MaintainAspectRatioSeamless,
|
||||
/// The texture coordinates range from 0 to 1.
|
||||
Stretch,
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EMRUKCoordModeV : uint8
|
||||
{
|
||||
/// The texture coordinates start at 0 and increase by 1 unit every meter.
|
||||
Metric,
|
||||
/// The texture coordinates are adjusted to the other dimensions to ensure the aspect ratio is maintained.
|
||||
MaintainAspectRatio,
|
||||
/// The texture coordinates range from 0 to 1.
|
||||
Stretch,
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EMRUKSpawnerSelectionMode : uint8
|
||||
{
|
||||
/// Pick one at random.
|
||||
Random,
|
||||
/// Pick the closest size.
|
||||
ClosestSize,
|
||||
/// Used in the AMRUKAnchorActorSpawner to use allow for a custom selection mode.
|
||||
Custom,
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EMRUKSpawnerScalingMode : uint8
|
||||
{
|
||||
/// Stretch each axis to exactly match the size of the Plane/Volume.
|
||||
Stretch,
|
||||
/// Scale each axis by the same amount to maintain the correct aspect ratio.
|
||||
UniformScaling,
|
||||
/// Scale the X and Y axes uniformly but the Z scale can be different.
|
||||
UniformXYScale,
|
||||
/// Don't perform any scaling.
|
||||
NoScaling,
|
||||
/// Used in the AMRUKAnchorActorSpawner to use allow for a custom scaling.
|
||||
Custom,
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EMRUKAlignMode : uint8
|
||||
{
|
||||
/// Do not perform any alignment
|
||||
None,
|
||||
/// Align the bottom of the bounding boxes and center the rest
|
||||
Default,
|
||||
/// Center the bounding box in the anchor bounding box
|
||||
CenterOnCenter,
|
||||
/// Align the bottom of the bounding boxes and center the rest
|
||||
BottomOnBottom,
|
||||
/// Align the top of the bounding boxes and center the rest
|
||||
TopOnTop,
|
||||
/// Align the left of the bounding boxes and center the rest
|
||||
LeftOnLeft,
|
||||
/// Align the right of the bounding boxes and center the rest
|
||||
RightOnRight,
|
||||
/// Align the front of the bounding boxes and center the rest
|
||||
FrontOnFront,
|
||||
/// Align the back of the bounding boxes and center the rest
|
||||
BackOnBack,
|
||||
/// Align the top to the bottom of the anchor bounding box and center the rest
|
||||
BottomOnTop,
|
||||
/// Align the bottom to the top of the anchor bounding box and center the rest
|
||||
TopOnBottom,
|
||||
/// Align the left to the right of the anchor bounding box and center the rest
|
||||
LeftOnRight,
|
||||
/// Align the right to the left of the anchor bounding box and center the rest
|
||||
RightOnLeft,
|
||||
/// Align the front to the back of the anchor bounding box and center the rest
|
||||
FrontOnBack,
|
||||
/// Align the back to the front of the anchor bounding box and center the rest
|
||||
BackOnFront,
|
||||
/// Use custom alignment mode
|
||||
Custom,
|
||||
};
|
||||
|
||||
/**
|
||||
* This enum is used to specify the component type, scene anchors can either have plane or volume components associated with them or both.
|
||||
*/
|
||||
UENUM(meta = (Bitflags, UseEnumValuesAsMaskValuesInEditor = "true"))
|
||||
enum class EMRUKComponentType
|
||||
{
|
||||
/// No component type.
|
||||
None = 0 UMETA(Hidden),
|
||||
/// Plane component type.
|
||||
Plane = 1 << 0,
|
||||
/// Volume component type.
|
||||
Volume = 1 << 1,
|
||||
/// Mesh component type.
|
||||
Mesh = 1 << 2,
|
||||
/// All component types.
|
||||
All = Plane | Volume | Mesh UMETA(Hidden),
|
||||
};
|
||||
ENUM_CLASS_FLAGS(EMRUKComponentType);
|
||||
|
||||
/**
|
||||
* Describes a Raycast hit in the MRUK (Mixed Reality Utility Kit). This structure is created by the AMRUKAnchor::Raycast and AMRUKAnchor::RaycastAll methods. You can read the position where the raycast hit, the normal of the surface that was hit, and the distance from the origin to the raycast hit position.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct MRUTILITYKIT_API FMRUKHit
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The position where the raycast hit.
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
FVector HitPosition = FVector::ZeroVector;
|
||||
|
||||
/**
|
||||
* The normal of the surface that was hit.
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
FVector HitNormal = FVector::ZeroVector;
|
||||
|
||||
/**
|
||||
* The distance between the origin of the ray to the hit position.
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
float HitDistance = 0.0f;
|
||||
};
|
||||
|
||||
/**
|
||||
* Label filter to use in MRUK (Mixed Reality Utility Kit). You can use this to filter anchors by their labels.
|
||||
* use the IncludedLabels and ExcludedLabels list to specify which labels to include and exclude.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct MRUTILITYKIT_API FMRUKLabelFilter
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* If included labels is not empty then the anchor must have at
|
||||
* least one of the labels in this list.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
TArray<FString> IncludedLabels;
|
||||
|
||||
/**
|
||||
* Anchors with any of the labels in this exclusion list
|
||||
* will be ignored.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
TArray<FString> ExcludedLabels;
|
||||
|
||||
/**
|
||||
* Enum flags representing component types to include, by default include all component types.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit", meta = (Bitmask, BitmaskEnum = "EMRUKComponentType"))
|
||||
int32 ComponentTypes = static_cast<int32>(EMRUKComponentType::All);
|
||||
|
||||
/**
|
||||
* Check if the labels pass the given label filter
|
||||
* @param Labels The labels to check.
|
||||
* @return Whether the filter passes or not.
|
||||
*/
|
||||
bool PassesFilter(const TArray<FString>& Labels) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a configuration for adjusting the UV texture coordinates of a plane.
|
||||
*
|
||||
* It contains properties to specify an offset and scale to be applied to the UV texture coordinates.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct MRUTILITYKIT_API FMRUKPlaneUV
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Offset applied to the UV texture coordinates.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
FVector2D Offset = FVector2D::ZeroVector;
|
||||
|
||||
/**
|
||||
* Scale applied to the UV texture coordinates.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
FVector2D Scale = FVector2D::UnitVector;
|
||||
};
|
||||
|
||||
/**
|
||||
* Texture coordinate modes for MRUK (Mixed Reality Utility Kit). You can use this to specify the texture coordinate mode for the U and V directions.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct MRUTILITYKIT_API FMRUKTexCoordModes
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Texture Coordinate mode for the U direction.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
EMRUKCoordModeU U = EMRUKCoordModeU::Metric;
|
||||
|
||||
/**
|
||||
* Texture Coordinate mode for the V direction.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
EMRUKCoordModeV V = EMRUKCoordModeV::Metric;
|
||||
};
|
||||
|
||||
/**
|
||||
* This struct represents a configuration for spawning an actor in the scene.
|
||||
*
|
||||
* It contains properties to specify the class of the actor to spawn, whether to match the aspect ratio of the volume,
|
||||
* whether to calculate the facing direction of the actor, and what scaling and alignment modes to apply to the actor.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct MRUTILITYKIT_API FMRUKSpawnActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The class of actor to spawn.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
TSubclassOf<AActor> Actor;
|
||||
|
||||
/**
|
||||
* When match aspect ratio is enabled then the actor will be rotated
|
||||
* to try and match the aspect ratio of the volume as closely as possible.
|
||||
* This is most useful for long and thin volumes, keep this disabled for
|
||||
* objects with an aspect ratio close to 1:1. Only applies to volumes.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
bool MatchAspectRatio = false;
|
||||
|
||||
/**
|
||||
* When calculate facing direction is enabled the actor will be rotated to
|
||||
* face away from the closest wall. If match aspect ratio is also enabled
|
||||
* then that will take precedence and it will be constrained to a choice
|
||||
* between 2 directions only. Only applies to volumes.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
bool CalculateFacingDirection = false;
|
||||
|
||||
/**
|
||||
* Set what scaling mode to apply to the actor. By default the actor will
|
||||
* be stretched to fit the size of the plane/volume. But in some cases
|
||||
* this may not be desirable and can be customized here.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
EMRUKSpawnerScalingMode ScalingMode = EMRUKSpawnerScalingMode::Stretch;
|
||||
|
||||
/**
|
||||
* Set what alignment mode to apply to the actor. By default the actor will
|
||||
* be aligned that its bounding box matches the one from the anchor.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
EMRUKAlignMode AlignMode = EMRUKAlignMode::Default;
|
||||
};
|
||||
|
||||
/**
|
||||
* This enum is used to specify the fallback behaviour when spawning an scene actor.
|
||||
* Specify whether to fallback to a procedural mesh or not.
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EMRUKFallbackToProceduralOverwrite : uint8
|
||||
{
|
||||
/// Don't override the fallback to procedural standard behaviour.
|
||||
Default,
|
||||
/// Fallback to a procedural mesh.
|
||||
Fallback,
|
||||
/// Don't fallback to a procedural mesh.
|
||||
NoFallback,
|
||||
};
|
||||
|
||||
/**
|
||||
* Holds a configuration for spawning a group of actors.
|
||||
*
|
||||
* It contains properties to specify a list of actors to choose from, the selection mode when multiple actors are specified,
|
||||
* and whether to fall back to spawning a procedural mesh if no actor class has been specified for this label.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct MRUTILITYKIT_API FMRUKSpawnGroup
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* List of actors to choose from, multiple actors can be specified and
|
||||
* the selection criteria will be determined by the SelectionMode option.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
TArray<FMRUKSpawnActor> Actors;
|
||||
|
||||
/**
|
||||
* Set the selection mode when multiple different actors are specified.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
EMRUKSpawnerSelectionMode SelectionMode = EMRUKSpawnerSelectionMode::Random;
|
||||
|
||||
/**
|
||||
* Control if there should happen a fallback to spawning a procedural mesh
|
||||
* in case no actor class has been specified for this label. The global
|
||||
* fallback behaviour can be specified in the AMRUKAnchorActorSpawner.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
EMRUKFallbackToProceduralOverwrite FallbackToProcedural = EMRUKFallbackToProceduralOverwrite::Default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements the settings for the MRUtilityKit plugin. This is Unreal specific and not part of the MR Utility Kit library.
|
||||
*/
|
||||
UCLASS(config = Game, defaultconfig)
|
||||
class MRUTILITYKIT_API UMRUKSettings : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UMRUKSettings(const FObjectInitializer& obj);
|
||||
|
||||
/**
|
||||
* When world locking is enabled the position of the VR Pawn will be adjusted each frame to ensure
|
||||
* the room anchors are where they should be relative to the camera position. This is necessary to
|
||||
* ensure the position of the virtual objects in the world do not get out of sync with the real world.
|
||||
*/
|
||||
UPROPERTY(config, EditAnywhere, Category = "MR Utility Kit")
|
||||
bool EnableWorldLock = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* MRUK (Mixed Reality Utility Kit) labels. These are the labels that are used by the MR Utility Kit library.
|
||||
* Those labels are used to identify the different types of objects in the scene, such as walls, floors, etc.
|
||||
*
|
||||
* Furthermore you also use those labels to filter, for queries and other tools such as the Raycast and RaycastAll methods.
|
||||
*/
|
||||
struct MRUTILITYKIT_API FMRUKLabels
|
||||
{
|
||||
static const FString Floor;
|
||||
static const FString WallFace;
|
||||
static const FString InvisibleWallFace;
|
||||
static const FString Ceiling;
|
||||
static const FString DoorFrame;
|
||||
static const FString WindowFrame;
|
||||
static const FString Couch;
|
||||
static const FString Table;
|
||||
static const FString Screen;
|
||||
static const FString Bed;
|
||||
static const FString Lamp;
|
||||
static const FString Plant;
|
||||
static const FString Storage;
|
||||
static const FString WallArt;
|
||||
static const FString GlobalMesh;
|
||||
static const FString Other;
|
||||
};
|
||||
|
||||
/**
|
||||
* This spawnmode controls how the MR Utility Kit handles spawning actors in the scene, either for all rooms, only for the current room or not at all.
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EMRUKSpawnMode : uint8
|
||||
{
|
||||
/// Do not spawn anything on loading a scene or rooms.
|
||||
None = 0,
|
||||
|
||||
/// Will only take the current room into account. This enables legacy single room behaviour. Keep in mind that if your
|
||||
/// experience loads multiple rooms and you use that mode the behaviour might be undefined.
|
||||
CurrentRoomOnly,
|
||||
|
||||
/// Spawn in every room and keep on spawning whenever a new room was discovered.
|
||||
AllRooms
|
||||
};
|
||||
|
||||
/**
|
||||
* UE Module interface impelmentation
|
||||
*/
|
||||
class FMRUKModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
};
|
||||
@@ -0,0 +1,281 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "Dom/JsonObject.h"
|
||||
#include "MRUtilityKitAnchorActorSpawner.h"
|
||||
|
||||
#include "OculusXRAnchorTypes.h"
|
||||
#include "ProceduralMeshComponent.h"
|
||||
#include "MRUtilityKitAnchor.generated.h"
|
||||
|
||||
class AMRUKRoom;
|
||||
class UMRUKAnchorData;
|
||||
|
||||
/**
|
||||
* Represents an anchor in the Mixed Reality Utility Kit. This combines an Unreal actor with the scene anchor.
|
||||
* The actor is placed at the position of the anchor and the actor's rotation is set to match the rotation of the anchor.
|
||||
* Provides functions to check if a position is inside the volume or plane of the anchor, raycast against the anchor, etc...
|
||||
* @see https://developer.oculus.com/documentation/unreal/unreal-spatial-anchors/
|
||||
* for more information about anchors in the Mixed Reality Utility Kit.
|
||||
*/
|
||||
UCLASS(ClassGroup = MRUtilityKit, meta = (DisplayName = "MR Utility Kit Anchor"))
|
||||
class MRUTILITYKIT_API AMRUKAnchor : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* The space handle of this anchor
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
FOculusXRUInt64 SpaceHandle;
|
||||
|
||||
/**
|
||||
* The anchors UUID
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
FOculusXRUUID AnchorUUID;
|
||||
|
||||
/**
|
||||
* The semantic classification of the anchor, also sometimes refered to as labels for short.
|
||||
* This can be for example FLOOR, COUCH, TABLE, SCREEN, BED, LAMP, etc...
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
TArray<FString> SemanticClassifications;
|
||||
|
||||
/**
|
||||
* If the anchor has a plane attached to it, this represents the bounds of that plane in
|
||||
* local coordinate space.
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
FBox2D PlaneBounds{ ForceInit };
|
||||
|
||||
/**
|
||||
* If the anchor has a plane attached to it, this represents the boundary of it in
|
||||
* local coordinate space. For rectangular boundaries this will be the same as the
|
||||
* PlaneBounds.
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
TArray<FVector2D> PlaneBoundary2D;
|
||||
|
||||
/**
|
||||
* If the anchor has a volume attached to it, this represents the bounds of that volume in
|
||||
* local coordinate space.
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
FBox VolumeBounds{ ForceInit };
|
||||
|
||||
/**
|
||||
* Procedural mesh that is generated from the anchor geometry.
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
TObjectPtr<UProceduralMeshComponent> ProceduralMeshComponent;
|
||||
|
||||
/**
|
||||
* Pointer to the parent anchor, e.g. if this is a door or window frame the parent will
|
||||
* be a wall. If this is a screen it could have a desk parent.
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
TObjectPtr<AMRUKAnchor> ParentAnchor;
|
||||
|
||||
/**
|
||||
* Array of all children attached to it, e.g. if this is a wall, it could have an array
|
||||
* of door/window frames. If this is a desk it could have an array of screens on it.
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
TArray<TObjectPtr<AMRUKAnchor>> ChildAnchors;
|
||||
|
||||
/**
|
||||
* The room this anchor is placed in.
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
TObjectPtr<AMRUKRoom> Room;
|
||||
|
||||
/**
|
||||
* Check if a 2D position is within the boundary of the plane. The position should be in
|
||||
* the local coordinate system NOT world coordinates.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool IsPositionInBoundary(const FVector2D& Position);
|
||||
|
||||
/**
|
||||
* Generate a uniform random position within the boundary of the plane.
|
||||
* @return The random position in local coordinate space.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
FVector GenerateRandomPositionOnPlane();
|
||||
|
||||
/**
|
||||
* Generate a uniform random position within the boundary of the plane from a random stream.
|
||||
* @param RandomStream A random generator used to generate the position on the plane.
|
||||
* @return The random position in local coordinate space.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
FVector GenerateRandomPositionOnPlaneFromStream(const FRandomStream& RandomStream);
|
||||
|
||||
/**
|
||||
* Cast a ray and return the closest hit against the volume and plane bounds.
|
||||
* @param Origin Origin The origin of the ray.
|
||||
* @param Direction Direction The direction of the ray.
|
||||
* @param MaxDist The maximum distance the ray should travel.
|
||||
* @param OutHit The closest hit.
|
||||
* @param ComponentTypes The component types to include in the raycast.
|
||||
* @return Whether the ray hit anything
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool Raycast(const FVector& Origin, const FVector& Direction, float MaxDist, FMRUKHit& OutHit, UPARAM(meta = (Bitmask, BitmaskEnum = "EMRUKComponentType")) int32 ComponentTypes = 7 /* EMRUKComponentType::All */);
|
||||
static_assert(static_cast<int32>(EMRUKComponentType::All) == 7, "If this changes, please update the hardcoded default parameter in the Raycast function above");
|
||||
|
||||
/**
|
||||
* Cast a ray and collect hits against the volume and plane bounds. The order of the hits in the array is not specified.
|
||||
* @param Origin Origin The origin of the ray.
|
||||
* @param Direction Direction The direction of the ray.
|
||||
* @param MaxDist The maximum distance the ray should travel.
|
||||
* @param OutHits The hits the ray collected.
|
||||
* @param ComponentTypes The component types to include in the raycast.
|
||||
* @return Whether the ray hit anything
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool RaycastAll(const FVector& Origin, const FVector& Direction, float MaxDist, TArray<FMRUKHit>& OutHits, UPARAM(meta = (Bitmask, BitmaskEnum = "EMRUKComponentType")) int32 ComponentTypes = 7 /* EMRUKComponentType::All */);
|
||||
static_assert(static_cast<int32>(EMRUKComponentType::All) == 7, "If this changes, please update the hardcoded default parameter in the RaycastAll function above");
|
||||
|
||||
/**
|
||||
* Attach a procedural mesh to the anchor. The mesh will match the size, position and shape of the volume and/or plane
|
||||
* if they are set.
|
||||
* @param PlaneUVAdjustments Scale and offset to apply to the UV texture coordinates. If more than one is specified
|
||||
* then multiple UV texture coordinates are created (up to 4) and adjustments applied to
|
||||
* each. This can be left empty in which case a single set of UV texture coordinates are
|
||||
* created in the range 0 to 1 for the plane.
|
||||
* @param CutHoleLabels Labels for which the generated mesh should have holes. Only works with planes.
|
||||
* @param GenerateCollision Whether to generate collision geometry or not
|
||||
* @param ProceduralMaterial Material to use on the procedural generated mesh.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (AutoCreateRefTerm = "PlaneUVAdjustments", DeprecatedFunction, DeprecationMessage = "Use GenerateProceduralMesh instead."))
|
||||
void AttachProceduralMesh(TArray<FMRUKPlaneUV> PlaneUVAdjustments, const TArray<FString>& CutHoleLabels, bool GenerateCollision = true, UMaterialInterface* ProceduralMaterial = nullptr);
|
||||
|
||||
/**
|
||||
* Generate a procedural mesh for the anchor. The mesh will match the size, position and shape of the volume and/or plane
|
||||
* if they are set.
|
||||
* @param ProceduralMesh The procedural mesh component that should be used to store the generated mesh.
|
||||
* @param PlaneUVAdjustments Scale and offset to apply to the UV texture coordinates. If more than one is specified
|
||||
* then multiple UV texture coordinates are created (up to 4) and adjustments applied to
|
||||
* each. This can be left empty in which case a single set of UV texture coordinates are
|
||||
* created in the range 0 to 1 for the plane.
|
||||
* @param CutHoleLabels Labels for which the generated mesh should have holes. Only works with planes.
|
||||
* @param GenerateCollision Whether to generate collision geometry or not
|
||||
* @param Offset A offset to make the procedural mesh slightly bigger or smaller than the anchors volume/plane.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (AutoCreateRefTerm = "PlaneUVAdjustments"))
|
||||
void GenerateProceduralAnchorMesh(UProceduralMeshComponent* ProceduralMesh, const TArray<FMRUKPlaneUV>& PlaneUVAdjustments, const TArray<FString>& CutHoleLabels, bool PreferVolume = false, bool GenerateCollision = true, double Offset = 0.0);
|
||||
|
||||
/**
|
||||
* Check if the anchor has the given label.
|
||||
* @param Label The label to check.
|
||||
* @return Whether the anchor has the given label.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool HasLabel(const FString& Label) const;
|
||||
|
||||
/**
|
||||
* Check if the anchor has any of the given labels.
|
||||
* @param Labels The labels to check.
|
||||
* @return Whether the anchor has any of the given labels.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool HasAnyLabel(const TArray<FString>& Labels) const;
|
||||
|
||||
/**
|
||||
* Check if the anchor passes the given label filter
|
||||
* @param LabelFilter The labels to check.
|
||||
* @return Whether the anchor has any of the given labels.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool PassesLabelFilter(const FMRUKLabelFilter& LabelFilter) const;
|
||||
|
||||
/**
|
||||
* Calculate the closest surface position on this anchor.
|
||||
* @param TestPosition The position in world space for which the closes surface position should be obtained.
|
||||
* @param OutSurfacePosition The closest surface position
|
||||
* @return The distance between TestPosition and OutSurfacePosition
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
double GetClosestSurfacePosition(const FVector& TestPosition, FVector& OutSurfacePosition);
|
||||
|
||||
/**
|
||||
* Checks if the given position is on or inside the volume bounds.
|
||||
* Floor, ceiling and wall anchors will be excluded from the search.
|
||||
* @param Position The position in world space to check
|
||||
* @param TestVerticalBounds Whether the vertical bounds should be checked or not
|
||||
* @param Tolerance Tolerance
|
||||
* @return The anchor the WorldPosition is in. A null pointer otherwise.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool IsPositionInVolumeBounds(const FVector& Position, bool TestVerticalBounds = true, double Tolerance = 0.0);
|
||||
|
||||
/**
|
||||
* Gets a natural “forward” direction for anchors; for planes, this is always Z-forward.
|
||||
* For volumes, it’s the X/Y cardinal axis that aligns best with the normal of the closest wall.
|
||||
* @return The forward facing direction.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
FVector GetFacingDirection() const;
|
||||
|
||||
/**
|
||||
* Spawn a mesh on the position of this anchor.
|
||||
* The actor should have Z as up, Y as right and X as forward.
|
||||
* @param ActorClass The Class to spawn at the anchors position.
|
||||
* @param MatchAspectRatio If true the actor will be rotated to best match the aspect ratio of the volume (applies to volumes only).
|
||||
* @param CalculateFacingDirection If true then actor will be rotated to face away from the closest wall (applies to volumes only).
|
||||
* @param ScalingMode Sets how to scale the actor to fit the size of the volume/plane.
|
||||
* @return The spawned actor or null if nothing was spawned.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DeprecatedFunction, DeprecationMessage = "Use AMRUKAnchorActorSpawner instead."), Category = "MR Utility Kit")
|
||||
AActor* SpawnInterior(const TSubclassOf<class AActor>& ActorClass, bool MatchAspectRatio = false, bool CalculateFacingDirection = false, EMRUKSpawnerScalingMode ScalingMode = EMRUKSpawnerScalingMode::Stretch);
|
||||
|
||||
public:
|
||||
AMRUKAnchor(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
/**
|
||||
* Load the anchor from a MRUKAnchorData. This is used to load or update the anchor from device or from a JSON file.
|
||||
*
|
||||
* @param AnchorData The data to load from.
|
||||
* @return true if the anchor was loaded successfully.
|
||||
* @return false if the anchor could not be loaded.
|
||||
*/
|
||||
bool LoadFromData(UMRUKAnchorData* AnchorData);
|
||||
|
||||
/**
|
||||
* Attach a procedural mesh to the anchor. The mesh will match the size, position and shape of the volume and/or plane.
|
||||
*
|
||||
* @param CutHoleLabels Labels for which the generated mesh should have holes. Only works with planes. Example values: "WindowFrame", "DoorFrame".
|
||||
* @param GenerateCollision Whether to generate collision geometry or not.
|
||||
* @param ProceduralMaterial Material to use on the procedural generated mesh.
|
||||
*/
|
||||
void AttachProceduralMesh(const TArray<FString>& CutHoleLabels = {}, bool GenerateCollision = true, UMaterialInterface* ProceduralMaterial = nullptr);
|
||||
|
||||
TSharedRef<FJsonObject> JsonSerialize();
|
||||
|
||||
protected:
|
||||
void EndPlay(EEndPlayReason::Type Reason) override;
|
||||
|
||||
private:
|
||||
bool RayCastPlane(const FRay& LocalRay, float MaxDist, FMRUKHit& OutHit);
|
||||
bool RayCastVolume(const FRay& LocalRay, float MaxDist, FMRUKHit& OutHit);
|
||||
|
||||
struct TriangulatedMeshCache
|
||||
{
|
||||
TArray<FVector2D> Vertices;
|
||||
TArray<int32> Triangles;
|
||||
TArray<float> Areas;
|
||||
float TotalArea;
|
||||
|
||||
void Clear();
|
||||
};
|
||||
|
||||
UPROPERTY()
|
||||
AActor* Interior = nullptr;
|
||||
|
||||
TOptional<TriangulatedMeshCache> CachedMesh;
|
||||
};
|
||||
@@ -0,0 +1,322 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "MRUtilityKit.h"
|
||||
#include "MRUtilityKitAnchorActorSpawner.generated.h"
|
||||
|
||||
extern const FName GMRUK_PROCEDURAL_ANCHOR_MESH_TAG;
|
||||
|
||||
class AMRUKAnchor;
|
||||
/**
|
||||
* Spawns meshes on anchor positions.
|
||||
* If the out of the box functionality doesn't match your goals the AnchorActorSpawner provides way to inject
|
||||
* custom spawning logic into every step of it's spawning process by overwriting certain functions.
|
||||
* For this please take a look at SpawnAnchorActorsForRoom(), SpawnAnchorActorForLabel(), and SpawnAnchorActor().
|
||||
*/
|
||||
UCLASS(ClassGroup = MRUtilityKit, meta = (DisplayName = "MR Utility Kit Anchor Actor Spawner"))
|
||||
class MRUTILITYKIT_API AMRUKAnchorActorSpawner : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnInteriorSpawned, AMRUKRoom*, Room);
|
||||
|
||||
/**
|
||||
* Event that gets fired when the interior spawner finished spawning actors.
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
|
||||
FOnInteriorSpawned OnActorsSpawned;
|
||||
|
||||
/**
|
||||
* Seed to use for the random generator that decideds wich actor class to
|
||||
* spawn if there a given multiple for a label.
|
||||
* negative values will have the effect to initialize the random generator
|
||||
* to a random seed.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
int AnchorRandomSpawnSeed = -1;
|
||||
|
||||
/**
|
||||
* Whether actors should be spawned automatically after the mixed reality
|
||||
* utility kit has been initialized. This should not be changed after the scene has been loaded.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
EMRUKSpawnMode SpawnMode = EMRUKSpawnMode::CurrentRoomOnly;
|
||||
|
||||
/**
|
||||
* Material to use when falling back to procedural material.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
UMaterialInterface* ProceduralMaterial = nullptr;
|
||||
|
||||
/**
|
||||
* Whether or not the spawner should fallback to procedural meshes in case no actor
|
||||
* class has been defined for a label. This behaviour can be overwritten on the label
|
||||
* basis in SpawnGroups.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
bool ShouldFallbackToProcedural = true;
|
||||
|
||||
/**
|
||||
* Labels for which holes should be created in the parents plane mesh.
|
||||
* E.g. if holes are needed in the walls where the windows and doors are, specify DOOR_FRAME and WINDOW_FRAME.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
TArray<FString> CutHoleLabels;
|
||||
|
||||
/**
|
||||
* A map of Actor classes to spawn for the given label.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
TMap<FString, FMRUKSpawnGroup> SpawnGroups{
|
||||
{ FMRUKLabels::Bed, {} },
|
||||
{ FMRUKLabels::Ceiling, {} },
|
||||
{ FMRUKLabels::Couch, {} },
|
||||
{ FMRUKLabels::DoorFrame, {} },
|
||||
{ FMRUKLabels::Floor, {} },
|
||||
{ FMRUKLabels::Lamp, {} },
|
||||
{ FMRUKLabels::Plant, {} },
|
||||
{ FMRUKLabels::Screen, {} },
|
||||
{ FMRUKLabels::Storage, {} },
|
||||
{ FMRUKLabels::Table, {} },
|
||||
{ FMRUKLabels::WallArt, {} },
|
||||
{ FMRUKLabels::WallFace, {} },
|
||||
{ FMRUKLabels::InvisibleWallFace, { {}, EMRUKSpawnerSelectionMode::Random, EMRUKFallbackToProceduralOverwrite::NoFallback } },
|
||||
{ FMRUKLabels::WindowFrame, {} },
|
||||
{ FMRUKLabels::Other, {} },
|
||||
};
|
||||
|
||||
/**
|
||||
* Spawns the meshes for the given labels above on the anchor positions in each room.
|
||||
* There might be multiple actor classes for a give label. If thats the case a actor class will be chosen radomly.
|
||||
* The seed for this random generator can be set by AnchorRandomSpawnSeed.
|
||||
* This function will be called automatically after the mixed reality utility kit initialized unless
|
||||
* the option SpawnOnStart is set to false.
|
||||
* If there is no actor class specified for a label then a procedural mesh matching the anchors volume and plane
|
||||
* will be generated.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void SpawnActors(AMRUKRoom* Room);
|
||||
|
||||
/**
|
||||
* Return all spawned actors from the give room.
|
||||
* @param Room The room from which the actors should be returned
|
||||
* @param Actors The spawned actors.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void GetSpawnedActorsByRoom(AMRUKRoom* Room, TArray<AActor*>& Actors);
|
||||
|
||||
/**
|
||||
* Return all spawned actors from all rooms.
|
||||
* @param Actors The spawned actors.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void GetSpawnedActors(TArray<AActor*>& Actors);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* This method gets called by the AnchorActorSpawner when it wants to spawn actors and procedural meshes in the room.
|
||||
* It's possible to overwrite this function in Blueprint or C++ to implement custom spawning logic.
|
||||
* The protected methods in the AnchorActorSpawner contain helper functions which can be useful when implementing
|
||||
* a custom spawning logic. When implementing a custom spawning logic you may want to use SpawnAnchorActor() to spawn
|
||||
* the actual actor and take care of it's orientation and scaling to match the anchors bounds.
|
||||
* @param Room The room to spawn actors for.
|
||||
* @param RandomStream A random stream to be used with the random selection mode.
|
||||
* @return A list of all spawned actors.
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "MR Utility Kit")
|
||||
TArray<AActor*> SpawnAnchorActorsInRoom(AMRUKRoom* Room, const FRandomStream& RandomStream);
|
||||
|
||||
virtual TArray<AActor*> SpawnAnchorActorsInRoom_Implementation(AMRUKRoom* Room, const FRandomStream& RandomStream);
|
||||
|
||||
/**
|
||||
* This method gets called by the default implementation of the SpawnAnchorActorsInRoom() for every label that should spawn a actor.
|
||||
* By overwriting this function it is possible to inject custom spawning logic for actors on a per label basis.
|
||||
* When implementing a custom spawning logic you may want to use SpawnAnchorActor() to spawn the actual actor and take care of it's
|
||||
* orientation and scaling to match the anchors bounds.
|
||||
* @param Anchor The anchor to spawn a actor for.
|
||||
* @param Label The label to spawn a actor for.
|
||||
* @param SpawnGroup Information on which actor should be spawned.
|
||||
* @param RandomStream A random stream for implementing the random selection logic.
|
||||
* @return The spawned actor.
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "MR Utility Kit")
|
||||
AActor* SpawnAnchorActorForLabel(AMRUKAnchor* Anchor, const FString& Label, const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream);
|
||||
|
||||
virtual AActor* SpawnAnchorActorForLabel_Implementation(AMRUKAnchor* Anchor, const FString& Label, const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream);
|
||||
|
||||
/**
|
||||
* This method gets called by the default implementation of SpawnAnchorActorForLabel() to spawn the anchor and orient and scale
|
||||
* it correct to the given anchor. If you are planning to implement a custom spawning logic you likely want to use this function
|
||||
* in the end to actually spawn the actor as it takes care of orientation and scaling of the actor with regards to the anchor bounds.
|
||||
* @param Anchor The anchor to spawn the actor for.
|
||||
* @param SpawnActor Information on which actor should be spawned.
|
||||
* @return The spawned actor.
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "MR Utility Kit")
|
||||
AActor* SpawnAnchorActor(AMRUKAnchor* Anchor, const FMRUKSpawnActor& SpawnActor);
|
||||
|
||||
virtual AActor* SpawnAnchorActor_Implementation(AMRUKAnchor* Anchor, const FMRUKSpawnActor& SpawnActor);
|
||||
|
||||
/**
|
||||
* Override this method to inject custom scaling logic into the orientation process of an actor. The scale that this method returns
|
||||
* gets used to scale the actor that will be spawned.
|
||||
* @param Anchor The anchor for which the actor gets spawned.
|
||||
* @param SpawnedActor The actor that gets spawned.
|
||||
* @param StretchedScale The scale that would need to be applied to the actor to make it match with the bounding box of the anchor.
|
||||
* In case it's a plane anchor only the X and Y component of the scale are relevant.
|
||||
* @return The scale that should be applied to the actor. In case it's a plane anchor only the X and Y component are relevant.
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "MR Utility Kit")
|
||||
FVector ComputeCustomScaling(AMRUKAnchor* Anchor, AActor* SpawnedActor, const FVector& StretchedScale);
|
||||
|
||||
virtual FVector ComputeCustomScaling_Implementation(AMRUKAnchor* Anchor, AActor* SpawnedActor, const FVector& StretchedScale);
|
||||
|
||||
/**
|
||||
* Override this method to inject custom actor selection logic. This will be called for every actor that gets spawned by the AMRUKAnchorActorSpawner.
|
||||
* @param Anchor The anchor for which a actor should be spawned
|
||||
* @param SpawnGroup The group of actors that can be used for decision making.
|
||||
* @param RandomStream A random stream to randomize outputs if necessary.
|
||||
* @param OutSpawnActor The actor which should be spawned.
|
||||
* @return Whether the selection process was successful or not.
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool SelectSpawnActorCustom(AMRUKAnchor* Anchor, const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream, FMRUKSpawnActor& OutSpawnActor);
|
||||
|
||||
virtual bool SelectSpawnActorCustom_Implementation(AMRUKAnchor* Anchor, const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream, FMRUKSpawnActor& OutSpawnActor);
|
||||
|
||||
/**
|
||||
* Override this method to inject custom scaling logic into the orientation process of an actor. The scale that this method returns
|
||||
* gets used to scale the actor that will be spawned.
|
||||
* @param Anchor The anchor for which the actor gets spawned.
|
||||
* @param Actor The actor that gets spawned.
|
||||
* @param ChildBounds the rotated bounding box of the actor that should be spawned. For planes only X and Y components are relevant.
|
||||
* @param Scale The scale that will be applied to the actor that will be spawned in place of the anchor. For planes only X and Y components are relevant.
|
||||
* @return The offset that should be applied to the actor. In case it's a plane anchor only the X and Y component are relevant.
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "MR Utility Kit")
|
||||
FVector ComputeCustomAlign(AMRUKAnchor* Anchor, AActor* Actor, const FBox& ChildBounds, const FVector& Scale);
|
||||
|
||||
virtual FVector ComputeCustomAlign_Implementation(AMRUKAnchor* Anchor, AActor* Actor, const FBox& ChildBounds, const FVector& Scale);
|
||||
|
||||
/**
|
||||
* Check if for the given SpawnGroup a procedural mesh should be spawned.
|
||||
* @param SpawnGroup The spawn group to check
|
||||
* @return Whether a procedural mesh should be spawned or not
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool ShouldAnchorFallbackToProceduralMesh(const FMRUKSpawnGroup& SpawnGroup) const;
|
||||
|
||||
/**
|
||||
* Check if there should be spawned a actor for the given label. This function may return false in case
|
||||
* the spawner should fallback to a procedural mesh.
|
||||
* @param Anchor The anchor where the actor should be spawned
|
||||
* @param Label The label of the anchor
|
||||
* @param OutSpawnGroup Will be set in case a actor should be spawned
|
||||
* @return Whether or not a actor should be spawned for the anchor
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool ShouldSpawnActorForAnchor(AMRUKAnchor* Anchor, const FString& Label, FMRUKSpawnGroup& OutSpawnGroup) const;
|
||||
|
||||
/**
|
||||
* Spawn a procedural mesh for all walls if no wall actor is given to the spawner.
|
||||
* This will take care of generating seamless UVs for the walls.
|
||||
* @param Room The room to spawn in.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
TArray<AActor*> SpawnProceduralMeshesOnWallsIfNoWallActorGiven(AMRUKRoom* Room);
|
||||
|
||||
/**
|
||||
* Spawn a procedural mesh for the floor if no floor actor is given to the spawner.
|
||||
* @param Room The room to spawn in.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
AActor* SpawnProceduralMeshOnFloorIfNoFloorActorGiven(AMRUKRoom* Room);
|
||||
|
||||
/**
|
||||
* Spawn a procedural mesh for the ceiling if no ceiling actor is given to the spawner.
|
||||
* @param Room The room to spawn in.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
AActor* SpawnProceduralMeshOnCeilingIfNoCeilingActorGiven(AMRUKRoom* Room);
|
||||
|
||||
/**
|
||||
* Spawn a procedural mesh for the given anchor if the settings on the AnchorActorSpawner say so.
|
||||
* @param Anchor The anchor for which the procedural mesh should be spawned
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
AActor* SpawnProceduralMeshForAnchorIfNeeded(AMRUKAnchor* Anchor);
|
||||
|
||||
/**
|
||||
* Spawn procedural meshes for every anchor that needs them. Including walls, ceiling and floor.
|
||||
* The method determines if procedural mesh should be spawned or not based on the settings of the
|
||||
* AnchorActorSpawner.
|
||||
* @param Room The room to spawn in.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
TArray<AActor*> SpawnProceduralMeshesInRoom(AMRUKRoom* Room);
|
||||
|
||||
/**
|
||||
* Select the SpawnActor based on the size that matches best the anchor bounds.
|
||||
* @param Anchor The anchor for which a actor should be spawned.
|
||||
* @param SpawnGroup The spawn group.
|
||||
* @param OutSpawnActor The found spawn actor.
|
||||
* @return True if a SpawnActor could be found. Otherwise, false.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool SelectSpawnActorClosestSize(AMRUKAnchor* Anchor, const FMRUKSpawnGroup& SpawnGroup, FMRUKSpawnActor& OutSpawnActor);
|
||||
|
||||
/**
|
||||
* Select the SpawnActor randomly
|
||||
* @param SpawnGroup The spawn group.
|
||||
* @param RandomStream The random stream to use for the random selection.
|
||||
* @param OutSpawnActor The found spawn actor.
|
||||
* @return True if a SpawnActor could be found. Otherwise, false.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool SelectSpawnActorRandom(const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream, FMRUKSpawnActor& OutSpawnActor);
|
||||
|
||||
/**
|
||||
* Select a SpawnActor from the SpawnGroup with respect to the given selection mode in SpawnGroup.
|
||||
* @param Anchor The anchor for which the actor should be spawned.
|
||||
* @param SpawnGroup The spawn group.
|
||||
* @param RandomStream The random stream
|
||||
* @param OutSpawnActor The found spawn actor
|
||||
* @return True if a spawn actor has been found. Otherwise, false.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool SelectSpawnActorFromSpawnGroup(AMRUKAnchor* Anchor, const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream, FMRUKSpawnActor& OutSpawnActor);
|
||||
|
||||
/**
|
||||
* Orient and scale the given actor to the anchors plane or volume bounds.
|
||||
* @param Anchor The anchor
|
||||
* @param Actor The actor which should be oriented and scaled to the given anchor.
|
||||
* @param ScalingMode The scaling mode that should be used when doing the matching.
|
||||
* @param bCalculateFacingDirection Whether or not the facing direction of the anchor should be calculated and used for the orientation process.
|
||||
* @param bMatchAspectRatio Whether or not the aspect ratio of the anchor should be matched.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void AttachAndFitActorToAnchor(AMRUKAnchor* Anchor, AActor* Actor, EMRUKSpawnerScalingMode ScalingMode, EMRUKAlignMode AlignMode, bool bCalculateFacingDirection, bool bMatchAspectRatio);
|
||||
|
||||
void BeginPlay() override;
|
||||
|
||||
UFUNCTION()
|
||||
void OnRoomCreated(AMRUKRoom* Room);
|
||||
|
||||
UFUNCTION()
|
||||
void OnRoomUpdated(AMRUKRoom* Room);
|
||||
|
||||
UFUNCTION()
|
||||
void OnRoomRemoved(AMRUKRoom* Room);
|
||||
|
||||
UFUNCTION()
|
||||
void RemoveActors(AMRUKRoom* Room);
|
||||
|
||||
private:
|
||||
// Room UUID to spawned actors in this room
|
||||
TMap<AMRUKRoom*, TArray<AActor*>> SpawnedActors;
|
||||
|
||||
int32 LastSeed = -1;
|
||||
};
|
||||
@@ -0,0 +1,173 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "Kismet/BlueprintAsyncActionBase.h"
|
||||
#include "OculusXRAnchorTypes.h"
|
||||
#include "MRUtilityKitBPLibrary.generated.h"
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FMRUKMeshSegment
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
TArray<FVector> Positions;
|
||||
TArray<int32> Indices;
|
||||
};
|
||||
|
||||
/**
|
||||
* Load the scene async from device.
|
||||
*/
|
||||
UCLASS()
|
||||
class MRUTILITYKIT_API UMRUKLoadFromDevice : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FMRUKLoaded);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (WorldContext = "WorldContext", BlueprintInternalUseOnly = "true"
|
||||
))
|
||||
static UMRUKLoadFromDevice* LoadSceneFromDeviceAsync(const UObject* WorldContext
|
||||
);
|
||||
|
||||
virtual void Activate() override;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FMRUKLoaded Success;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FMRUKLoaded Failure;
|
||||
|
||||
private:
|
||||
UFUNCTION(CallInEditor)
|
||||
void OnSceneLoaded(bool Succeeded);
|
||||
|
||||
TWeakObjectPtr<UWorld> World = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mixed Reality Utility Kit Blueprint Function Library.
|
||||
* See functions for further information.
|
||||
*/
|
||||
UCLASS()
|
||||
class MRUTILITYKIT_API UMRUKBPLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Load the global mesh from the device.
|
||||
* @param SpaceHandle Space handle of the room.
|
||||
* @param OutProceduralMesh Procedural mesh to load the triangle data in.
|
||||
* @param LoadCollision Whether to generate collision or not.
|
||||
* @param WorldContext Context of the world.
|
||||
* @return Whether the load was successful or not.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (WorldContext = "WorldContext"))
|
||||
static bool LoadGlobalMeshFromDevice(FOculusXRUInt64 SpaceHandle, UProceduralMeshComponent* OutProceduralMesh, bool LoadCollision, const UObject* WorldContext);
|
||||
|
||||
/**
|
||||
* Load the global mesh from a JSON string.
|
||||
* @param JsonString The string containing the JSON.
|
||||
* @param AnchorUUID Anchor UUID of the room
|
||||
* @param OutProceduralMesh Procedural mesh to load the triangle data in.
|
||||
* @param LoadCollision Whether to generate collision or not
|
||||
* @return Whether the load was successful or not.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
static bool LoadGlobalMeshFromJsonString(const FString& JsonString, FOculusXRUUID AnchorUUID, UProceduralMeshComponent* OutProceduralMesh, bool LoadCollision);
|
||||
|
||||
/**
|
||||
* (Re)Calculate Normals and Tangents of the given procedural mesh.
|
||||
* @param Mesh The procedural mesh.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
static void RecalculateProceduralMeshAndTangents(class UProceduralMeshComponent* Mesh);
|
||||
|
||||
/**
|
||||
* Check if the current Unreal Engine is the fork of Meta.
|
||||
* @return Whether its the fork or not.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "MR Utility Kit")
|
||||
static bool IsUnrealEngineMetaFork();
|
||||
|
||||
/**
|
||||
* Compute the centroid of a polygon that is defined by the points.
|
||||
* The centroid may be outside of the polygon in case the polygon is non convex.
|
||||
* @param PolygonPoints Points that define the polygon.
|
||||
* @return The centroid.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "MR Utility Kit")
|
||||
static FVector2D ComputeCentroid(const TArray<FVector2D>& PolygonPoints);
|
||||
|
||||
/**
|
||||
* In Unreal Engine, scale is always applied in the local space to avoid any skew.
|
||||
* This means that if you have a component which has a 90 degree rotation and is scaled, or any of its
|
||||
* children are scaled then the scale axes will not be applied as you would expect. This is can make it
|
||||
* very awkward to work with when trying to scale the actors to fit within the scene volumes. To work around
|
||||
* this problem, this function will attempt to adjust the scale axes recursively to match the expected behaviour.
|
||||
* This will only work reliably if the rotations involved are 90 degrees, if they are not then it will pick the closest axis.
|
||||
* @param SceneComponent The component where the scale should be set
|
||||
* @param UnRotatedScale The scale you would like to have without considering any rotations
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
static void SetScaleRecursivelyAdjustingForRotation(USceneComponent* SceneComponent, const FVector& UnRotatedScale);
|
||||
|
||||
/**
|
||||
* Compute the direction that faces away from the closest wall of the given anchor.
|
||||
* @param Anchor The anchor for which the direction should be computed.
|
||||
* @param OutCardinalAxisIndex The index of the computed cardinal axis. Can be either 0, 1, 2 or 3
|
||||
* @param ExcludedAxes Axes to exclude in the computation. Can contain 0, 1, 2, 3
|
||||
* @return The direction
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
static FVector ComputeDirectionAwayFromClosestWall(const AMRUKAnchor* Anchor, int& OutCardinalAxisIndex, const TArray<int> ExcludedAxes);
|
||||
|
||||
/**
|
||||
* Construct a 2D texture from a render target.
|
||||
* @param RenderTarget2D The render target from which the texture should be created.
|
||||
* @param Outer The (optional) outer object for the created texture.
|
||||
* @param TexName Name for the new texture.
|
||||
* @return The newly created texture.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
static UTexture2D* ConstructTexture2D(UTextureRenderTarget2D* RenderTarget2D, UObject* Outer, const FString& TexName);
|
||||
|
||||
/**
|
||||
* Extract a column from a matrix.
|
||||
* @param Matrix The matrix to use.
|
||||
* @param Index The column index.
|
||||
* @return The column of the matrix.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "MR Utility Kit")
|
||||
static FLinearColor GetMatrixColumn(const FMatrix& Matrix, int32 Index);
|
||||
|
||||
/**
|
||||
* Compute a grid by taking into account the room box geometry. E.g. create evenly spaced points on ceiling, floor and walls.
|
||||
* @param Room The room to use
|
||||
* @param MaxPointsCount The maximum number of points
|
||||
* @param PointsPerUnitX The density of points on the X axis
|
||||
* @param PointsPerUnitY The density of points on the Y axis
|
||||
* @param bIncludeFloor Whether or not to include the floor
|
||||
* @param bIncludeCeiling Whether or not to include the ceiling
|
||||
* @param bIncludeWalls Whether or not to include the walls
|
||||
* @return The computed points
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
static TArray<FVector> ComputeRoomBoxGrid(const AMRUKRoom* Room, int32 MaxPointsCount, double PointsPerUnitX = 1.0, double PointsPerUnitY = 1.0);
|
||||
|
||||
/**
|
||||
* Create mesh segments from the given mesh. This can be used for creating a destructible mesh system.
|
||||
* @param MeshPositions The mesh positions that should be segmented
|
||||
* @param MeshIndices The mesh indices that should be segmented
|
||||
* @param SegmentationPoints A set of points that should be used to calculate the segments
|
||||
* @param ReservedMin Reserved space from the lower part of the bound box
|
||||
* @param ReservedMax Reserved space from the upper part of the bounding box
|
||||
* @param OutSegments The segmented meshes that have been created from the given mesh
|
||||
* @param OutReservedSegment
|
||||
*/
|
||||
static void CreateMeshSegmentation(const TArray<FVector>& MeshPositions, const TArray<uint32>& MeshIndices,
|
||||
const TArray<FVector>& SegmentationPoints, const FVector& ReservedMin, const FVector& ReservedMax,
|
||||
TArray<FMRUKMeshSegment>& OutSegments, FMRUKMeshSegment& OutReservedSegment);
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/StaticMeshComponent.h"
|
||||
#include "MRUtilityKitBlobShadowComponent.generated.h"
|
||||
|
||||
/**
|
||||
* Adds a blob shadow below the actor.
|
||||
* The blob shadow will position and resize itself automatically during runtime.
|
||||
*/
|
||||
UCLASS(ClassGroup = MRUtilityKit, Blueprintable, BlueprintType, meta = (BlueprintSpawnableComponent, DisplayName = "MR Utility Kit Blob Shadow Component"))
|
||||
class MRUTILITYKIT_API UMRUKBlobShadowComponent : public UStaticMeshComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Controls the look of the blob shadow corners (0 = squared corners, 1 = rounded corners).
|
||||
*/
|
||||
UPROPERTY(Category = "MR Utility Kit|Aspect", EditAnywhere, BlueprintReadWrite, meta = (UIMin = "0", UIMax = "1"))
|
||||
float Roundness = 1.0f;
|
||||
|
||||
/**
|
||||
* Controls the look of the blob shadow alpha (0 = fully opaque, 1 = gradient from the center).
|
||||
*/
|
||||
UPROPERTY(Category = "MR Utility Kit|Aspect", EditAnywhere, BlueprintReadWrite, meta = (UIMin = "0", UIMax = "1"))
|
||||
float Gradient = 0.544f;
|
||||
|
||||
/**
|
||||
* Controls the curve of the blob shadow alpha gradient (only available if Gradient > 0).
|
||||
*/
|
||||
UPROPERTY(Category = "MR Utility Kit|Aspect", EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "Gradient > 0"))
|
||||
float GradientPower = 3.0f;
|
||||
|
||||
/**
|
||||
* Increase or decrease the calculated blob shadow size by a fixed amount.
|
||||
*/
|
||||
UPROPERTY(Category = "MR Utility Kit", EditAnywhere, BlueprintReadWrite)
|
||||
float ExtraExtent = -10.0f;
|
||||
|
||||
/**
|
||||
* Maximum distance the actor can be away from the ground until the blob shadow is not shown anymore.
|
||||
*/
|
||||
UPROPERTY(Category = "MR Utility Kit", EditAnywhere, BlueprintReadWrite)
|
||||
float MaxVerticalDistance = 100.f;
|
||||
|
||||
/**
|
||||
* Distance from the ground until the blob shadow starts to fade.
|
||||
*/
|
||||
UPROPERTY(Category = "MR Utility Kit", EditAnywhere, BlueprintReadWrite)
|
||||
float FadeDistance = 20.f;
|
||||
|
||||
/**
|
||||
* Only callable in the editor from the scene, will update the blob shadow size, position and material parameters
|
||||
* to give a preview how the blob shadow would look like.
|
||||
*/
|
||||
UFUNCTION(Category = "MR Utility Kit", CallInEditor)
|
||||
void UpdatePlaneSizeAndPosition();
|
||||
|
||||
public:
|
||||
UMRUKBlobShadowComponent();
|
||||
|
||||
void BeginPlay() override;
|
||||
void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||
void ComputeOwner2DBounds(FVector& Origin, FVector2D& Extent, double& Yaw) const;
|
||||
|
||||
protected:
|
||||
UPROPERTY()
|
||||
UMaterialInstanceDynamic* DynMaterial;
|
||||
};
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "OculusXRRoomLayoutManagerComponent.h"
|
||||
#include "Dom/JsonValue.h"
|
||||
#include "OculusXRAnchorsRequests.h"
|
||||
#include "MRUtilityKitData.generated.h"
|
||||
|
||||
/**
|
||||
* Actor to help finding the localization of actors.
|
||||
* It gets a list of all anchor queries that should be localized
|
||||
* and checks every tick if the anchor localization is there.
|
||||
* When the localization is complete, it will emit the event OnComplete.
|
||||
*
|
||||
* NOTE: Normally this should be a async task. However, the anchor data
|
||||
* can only be queried in game thread.
|
||||
*/
|
||||
UCLASS(ClassGroup = MRUtilityKit, Hidden)
|
||||
class MRUTILITYKIT_API AMRUKLocalizer : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnComplete, bool, Success);
|
||||
|
||||
/**
|
||||
* Event that gets fired when all anchors have been localized.
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
|
||||
FOnComplete OnComplete;
|
||||
|
||||
TArray<class UMRUKAnchorData*> AnchorsData;
|
||||
|
||||
AMRUKLocalizer();
|
||||
|
||||
void Tick(float DeltaTime) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* A datastrcture to hold the data of a single anchor. It also provides functions to load the data from device or json.
|
||||
*/
|
||||
UCLASS(ClassGroup = MRUtilityKit, Hidden)
|
||||
class MRUTILITYKIT_API UMRUKAnchorData : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
FOculusXRAnchorsDiscoverResult SpaceQuery;
|
||||
FTransform Transform;
|
||||
|
||||
FBox2D PlaneBounds;
|
||||
FBox VolumeBounds;
|
||||
|
||||
TArray<FString> SemanticClassifications;
|
||||
TArray<FVector2D> PlaneBoundary2D;
|
||||
|
||||
bool NeedAnchorLocalization = false;
|
||||
|
||||
void LoadFromDevice(const FOculusXRAnchorsDiscoverResult& AnchorsDiscoverResult);
|
||||
void LoadFromJson(const FJsonValue& Value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Load room data from device.
|
||||
* When all room data has been loaded, the OnComplete event will be fired.
|
||||
*/
|
||||
UCLASS(ClassGroup = MRUtilityKit, Hidden)
|
||||
class MRUTILITYKIT_API UMRUKRoomData : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnComplete, bool, Success);
|
||||
|
||||
/**
|
||||
* Event that gets fired after all room data has been loaded.
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
|
||||
FOnComplete OnComplete;
|
||||
|
||||
FOculusXRAnchorsDiscoverResult SpaceQuery;
|
||||
FOculusXRRoomLayout RoomLayout;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<TObjectPtr<UMRUKAnchorData>> AnchorsData;
|
||||
|
||||
UPROPERTY()
|
||||
AMRUKLocalizer* LocalizationActor = nullptr;
|
||||
|
||||
class UMRUKSceneData* SceneData;
|
||||
|
||||
void LoadFromDevice(UMRUKSceneData* Data, const FOculusXRAnchorsDiscoverResult& AnchorsDiscoverResult);
|
||||
void LoadFromJson(UMRUKSceneData* Data, const FJsonValue& Value);
|
||||
|
||||
private:
|
||||
void FinishQuery(bool Success);
|
||||
void RoomDataLoadedComplete(EOculusXRAnchorResult::Type Result);
|
||||
void RoomDataLoadedIncrementalResults(const TArray<FOculusXRAnchorsDiscoverResult>& DiscoverResults);
|
||||
UFUNCTION()
|
||||
void AnchorsInitialized(bool Success);
|
||||
};
|
||||
|
||||
/**
|
||||
* Load scene data from device.
|
||||
* When all scene data has been loaded, the OnComplete event will be fired.
|
||||
*/
|
||||
UCLASS(ClassGroup = MRUtilityKit, Hidden)
|
||||
class MRUTILITYKIT_API UMRUKSceneData : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnComplete, bool, Success);
|
||||
|
||||
/**
|
||||
* Event that gets fired after all scene data has been loaded.
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
|
||||
FOnComplete OnComplete;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<TObjectPtr<UMRUKRoomData>> RoomsData;
|
||||
|
||||
void LoadFromDevice();
|
||||
void LoadFromJson(const FString& Json);
|
||||
|
||||
private:
|
||||
int32 NumRoomsLeftToInitialize = 0;
|
||||
bool AnyRoomFailed = false;
|
||||
|
||||
void FinishQuery(bool Success);
|
||||
void SceneDataLoadedResult(EOculusXRAnchorResult::Type Result);
|
||||
void SceneDataLoadedComplete(const TArray<FOculusXRAnchorsDiscoverResult>& DiscoverResults);
|
||||
UFUNCTION()
|
||||
void RoomQueryComplete(bool Success);
|
||||
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "MRUtilityKitDebugComponent.generated.h"
|
||||
|
||||
/**
|
||||
* Various debugging utilities for the scene.
|
||||
* This component can for example attached to the player pawn. The various methods can
|
||||
* then be called on input from the pawn.
|
||||
*/
|
||||
UCLASS(ClassGroup = MRUtilityKit, meta = (BlueprintSpawnableComponent, DisplayName = "MR Utility Kit Debug Component"))
|
||||
class MRUTILITYKIT_API UMRUKDebugComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* The gizmo to show when visualizing an anchor.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
TSubclassOf<AActor> GizmoActorClass = nullptr;
|
||||
|
||||
/**
|
||||
* The text to show when visualizing an anchor.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
TSubclassOf<AActor> TextActorClass = nullptr;
|
||||
|
||||
/**
|
||||
* The scale that should be applied to the gizmo before displaying it.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
FVector GizmoScale = FVector(0.1);
|
||||
|
||||
/**
|
||||
* The scale that should be applied to the text before displaying it.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
FVector TextScale = FVector(0.5);
|
||||
|
||||
/**
|
||||
* Shoot a ray and display the anchors coordinate system and labels that was hit by the ray if any.
|
||||
* Call HideAnchor() to get rid of the displayed anchor.
|
||||
* @param Origin The ray origin.
|
||||
* @param Direction The ray direction.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void ShowAnchorAtRayHit(const FVector& Origin, const FVector& Direction);
|
||||
|
||||
/**
|
||||
* Hide the current anchor. This method needs only to be called to hide the anchor
|
||||
* that was displayed by ShowAnchorAtRayHit().
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void HideAnchor();
|
||||
|
||||
/**
|
||||
* Shoot a ray and display the anchors space that was hit by the ray if any.
|
||||
* Call HideAnchorSpace() to get rid of the displayed anchor space.
|
||||
* @param Origin The ray origin.
|
||||
* @param Direction The ray direction.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void ShowAnchorSpaceAtRayHit(const FVector& Origin, const FVector& Direction);
|
||||
|
||||
/**
|
||||
* Hide the current anchor space actor. This method needs only to be called to hide the
|
||||
* anchor space that was displayed by ShowAnchorAtRayHit().
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void HideAnchorSpace();
|
||||
|
||||
public:
|
||||
UMRUKDebugComponent();
|
||||
|
||||
void BeginPlay() override;
|
||||
void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||
void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
|
||||
private:
|
||||
UPROPERTY()
|
||||
TObjectPtr<AActor> ActiveGizmoActor = nullptr;
|
||||
UPROPERTY()
|
||||
TObjectPtr<AActor> ActiveTextActor = nullptr;
|
||||
UPROPERTY()
|
||||
TObjectPtr<AActor> ActiveAnchorSpaceActor = nullptr;
|
||||
|
||||
void OrientTextActorToPlayer() const;
|
||||
};
|
||||
@@ -0,0 +1,212 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MRUtilityKit.h"
|
||||
#include "MRUtilityKitBPLibrary.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "ProceduralMeshComponent.h"
|
||||
#include "Tasks/Task.h"
|
||||
#include "MRUtilityKitDestructibleMesh.generated.h"
|
||||
|
||||
/**
|
||||
* Destructible mesh component. Creates mesh segments for the given geometry.
|
||||
* The segments will be created async.
|
||||
* In addition, its possible to define areas that are indestructible.
|
||||
*/
|
||||
UCLASS(ClassGroup = MRUtilityKit, Blueprintable, BlueprintType, meta = (BlueprintSpawnableComponent, DisplayName = "MR Utility Kit Destructible Mesh Component"))
|
||||
class MRUTILITYKIT_API UMRUKDestructibleMeshComponent : public UProceduralMeshComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnMeshesGenerated);
|
||||
|
||||
UMRUKDestructibleMeshComponent(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
|
||||
FOnMeshesGenerated OnMeshesGenerated;
|
||||
|
||||
/**
|
||||
* Material to display on the global mesh
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
UMaterialInterface* GlobalMeshMaterial;
|
||||
|
||||
/**
|
||||
* Area on the top of the mesh that should be indestructible.
|
||||
* The area is given in centimeters 1.0 == 1 cm.
|
||||
* -1.0 means no reserved area.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
double ReservedTop = -1.0;
|
||||
|
||||
/**
|
||||
* Area on the bottom of the mesh that should be indestructible.
|
||||
* The area is given in centimeters 1.0 == 1 cm
|
||||
* -1.0 means no reserved area.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
double ReservedBottom = 30.0;
|
||||
|
||||
/**
|
||||
* Segment the given geometry into smaller chunks. For each chunk a procedural mesh component will be spawned and attached to the owning actor.
|
||||
* @param MeshPositions Positions of the mesh to segment
|
||||
* @param MeshIndices Indices of the mesh to segment
|
||||
* @param SegmentationPoints Points to use to determine the segments.
|
||||
*/
|
||||
void SegmentMesh(const TArray<FVector>& MeshPositions, const TArray<uint32>& MeshIndices, const TArray<FVector>& SegmentationPoints);
|
||||
|
||||
virtual void BeginPlay() override;
|
||||
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||
|
||||
private:
|
||||
UE::Tasks::TTask<TPair<TArray<FMRUKMeshSegment>, FMRUKMeshSegment>> TaskResult;
|
||||
};
|
||||
|
||||
/**
|
||||
* Actor that constructs a destructible mesh for the given room
|
||||
* The actor will automatically attach to the global mesh anchor of the given room to take it location and orientation.
|
||||
*/
|
||||
UCLASS(ClassGroup = MRUtilityKit, Blueprintable, BlueprintType, meta = (BlueprintSpawnableComponent, DisplayName = "MR Utility Kit Destructible Global Mesh"))
|
||||
class MRUTILITYKIT_API AMRUKDestructibleGlobalMesh : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
AMRUKDestructibleGlobalMesh();
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
UMRUKDestructibleMeshComponent* DestructibleMeshComponent;
|
||||
|
||||
/**
|
||||
* Density of mesh segments on the X axis.
|
||||
* Increase this value to get smaller cracks in the global mesh.
|
||||
* Decrease this value to get bigger cracks in the global mesh.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
double PointsPerUnitX = 1.0;
|
||||
|
||||
/**
|
||||
* How many segmentation points should be created at a maximum.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
int MaxPointsCount = 256;
|
||||
|
||||
/**
|
||||
* Density of mesh segments on the Y axis.
|
||||
* Increase this value to get smaller cracks in the global mesh.
|
||||
* Decrease this value to get bigger cracks in the global mesh.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
double PointsPerUnitY = 1.0;
|
||||
|
||||
/**
|
||||
* Create a destructible mesh for the given room. If the global mesh has not yet been loaded
|
||||
* this function will attempt to load the global mesh from the device.
|
||||
* @param Room The room
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void CreateDestructibleMesh(AMRUKRoom* Room = nullptr);
|
||||
|
||||
/**
|
||||
* Remove a segment of the global mesh. Takes care of not removing the reserved global mesh segment.
|
||||
* @param Mesh The mesh to remove
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void RemoveGlobalMeshSegment(UPrimitiveComponent* Mesh);
|
||||
};
|
||||
|
||||
/**
|
||||
* The destructible global mesh spawner allows to spawn (automatically) destructible global meshes
|
||||
* when new rooms are created.
|
||||
* A destructible global mesh is a version of the global mesh that can be destructed during runtime.
|
||||
* The bulk of the work is performed in UDestructibleMeshComponent. It will perform on start a one time
|
||||
* preprocessing step to segment the given global mesh into smaller chunks. After that the chunks can be used
|
||||
* during the game and removed (e.g. with ray casts) at any time during the game simulating as if the global
|
||||
* mesh would crack down. To enhance the visual quality when cracking the (e.g. removing mesh chunks) global mesh
|
||||
* a particle system could be used. The system allows to define areas that should be non destructible.
|
||||
*/
|
||||
UCLASS(ClassGroup = MRUtilityKit, Blueprintable, BlueprintType, meta = (BlueprintSpawnableComponent, DisplayName = "MR Utility Kit Destructible Global Mesh Spawner"))
|
||||
class MRUTILITYKIT_API AMRUKDestructibleGlobalMeshSpawner : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Whether destructible meshes should be spawned automatically.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
EMRUKSpawnMode SpawnMode = EMRUKSpawnMode::CurrentRoomOnly;
|
||||
|
||||
/**
|
||||
* Material to display on the global mesh
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
UMaterialInterface* GlobalMeshMaterial;
|
||||
|
||||
/**
|
||||
* Density of mesh segments on the X axis.
|
||||
* Increase this value to get smaller cracks in the global mesh.
|
||||
* Decrease this value to get bigger cracks in the global mesh.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
double PointsPerUnitX = 1.0;
|
||||
|
||||
/**
|
||||
* How many segmentation points should be created at a maximum.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
int MaxPointsCount = 256;
|
||||
|
||||
/**
|
||||
* Density of mesh segments on the Y axis.
|
||||
* Increase this value to get smaller cracks in the global mesh.
|
||||
* Decrease this value to get bigger cracks in the global mesh.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
double PointsPerUnitY = 1.0;
|
||||
|
||||
/**
|
||||
* Area on the top of the mesh that should be indestructible.
|
||||
* The area is given in centimeters 1.0 == 1 cm
|
||||
* -1.0 means no reserved area.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
double ReservedTop = -1.0;
|
||||
|
||||
/**
|
||||
* Area on the bottom of the mesh that should be indestructible.
|
||||
* The area is given in centimeters 1.0 == 1 cm
|
||||
* -1.0 means no reserved area.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
double ReservedBottom = 30.0;
|
||||
|
||||
void BeginPlay() override;
|
||||
|
||||
/**
|
||||
* Find the destructible mesh that has been spawned for the given room.
|
||||
* @param Room Room to look for the destructible mesh
|
||||
* @return The destructible mesh
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
AMRUKDestructibleGlobalMesh* FindDestructibleMeshForRoom(AMRUKRoom* Room);
|
||||
|
||||
/**
|
||||
* Add new destructible mesh for the given room. A mesh will only get spawned if no
|
||||
* destructible mesh has been spawned for the room yet.
|
||||
* @param Room The room.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
AMRUKDestructibleGlobalMesh* AddDestructibleGlobalMesh(AMRUKRoom* Room);
|
||||
|
||||
private:
|
||||
TMap<AMRUKRoom*, AMRUKDestructibleGlobalMesh*> SpawnedMeshes;
|
||||
|
||||
UFUNCTION()
|
||||
void OnRoomCreated(AMRUKRoom* Room);
|
||||
|
||||
UFUNCTION()
|
||||
void OnRoomRemoved(AMRUKRoom* Room);
|
||||
};
|
||||
@@ -0,0 +1,197 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MRUtilityKit.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "MRUtilityKitDistanceMapGenerator.generated.h"
|
||||
|
||||
const FName GMRUK_DISTANCE_MAP_ACTOR_TAG = TEXT("DistanceMapActor");
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EMRUKDistanceMapGenerationMode : uint8
|
||||
{
|
||||
// Do not generate a distance map
|
||||
None,
|
||||
/// Generate distance map only for the free space. E.g. The floor inside the room.
|
||||
FreeSpace,
|
||||
/// Generate the distance map only for the occupied space. E.g. outside the room and inside scene objects.
|
||||
OccupiedSpace,
|
||||
/// Generate the distance map for free space and occupied space.
|
||||
AllSpace,
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a distance map that can be used in materials to calculate the distance to various objects.
|
||||
* This can enable interesting effects. With the distance map you can get the distance from scene objects
|
||||
* or walls in a material shader.
|
||||
*
|
||||
* The Jump Flood Algorithm is used to generate the distance map. This is fast enough to regenerate
|
||||
* every tick.
|
||||
*
|
||||
* To capture a distance map after a room has been loaded call CaptureDistanceMap().
|
||||
* It will return a captured distance map. In case you already called CaptureDistanceMap()
|
||||
* you can receive the last captured distance map with GetDistanceMap(). No other setup is required.
|
||||
*
|
||||
* This class will create procedural meshes for every anchor to create a mask. These meshes have their
|
||||
* visibility set to scene capture only. That however means that if you place a scene capture component yourself
|
||||
* that the meshes will show up in your scene capture component. The actors that have the procedural meshes
|
||||
* attached are tagged with GMRUK_DISTANCE_MAP_ACTOR_TAG. In case you don't want them to show up in your
|
||||
* scene capture you can hide them by receiving all these actors with the tag GMRUK_DISTANCE_MAP_ACTOR_TAG
|
||||
* and add these to the scene captures hidden actors.
|
||||
*/
|
||||
UCLASS(ClassGroup = MRUtilityKit, meta = (DisplayName = "MR Utility Kit Distance Map Generator"))
|
||||
class MRUTILITYKIT_API AMRUKDistanceMapGenerator : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnReady);
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
|
||||
FOnReady OnReady;
|
||||
|
||||
/**
|
||||
* The mode in which the final distance map should be generated.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
EMRUKDistanceMapGenerationMode DistanceMapGenerationMode = EMRUKDistanceMapGenerationMode::FreeSpace;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
class USceneComponent* Root;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
class USceneCaptureComponent2D* SceneCapture2D;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
EMRUKSpawnMode SpawnMode = EMRUKSpawnMode::CurrentRoomOnly;
|
||||
|
||||
/**
|
||||
* First render target for jump flood algorithm.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
class UCanvasRenderTarget2D* RenderTarget1;
|
||||
|
||||
/**
|
||||
* Second render target for jump flood algorithm.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
class UCanvasRenderTarget2D* RenderTarget2;
|
||||
|
||||
/**
|
||||
* Render target for the final distance map
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
class UCanvasRenderTarget2D* DistanceMapRenderTarget;
|
||||
|
||||
/**
|
||||
* Material to render a mask that gets used to calculate the distance map.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
class UMaterialInterface* MaskMaterial;
|
||||
|
||||
/**
|
||||
* Material that executes a pass of the jump flood algorithm.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
class UMaterialInterface* JFPassMaterial;
|
||||
|
||||
/**
|
||||
* Material to render final distance map
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
class UMaterialInterface* DistanceMapFreeSpaceMaterial;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
class UMaterialInterface* DistanceMapOccupiedSpaceMaterial;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
class UMaterialInterface* DistanceMapAllSpaceMaterial;
|
||||
|
||||
/**
|
||||
* Capture the distance map.
|
||||
* @return The captured distance map.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
UTexture* CaptureDistanceMap();
|
||||
|
||||
/**
|
||||
* Create mask meshes for the given room.
|
||||
* These mask meshes are needed for the distance map to be rendered. It should only be called once before
|
||||
* CaptureDistanceMap in case the SpawnMode has been set to None.
|
||||
* The operation that this function executes is expensive. It only needs to be called after the room has been
|
||||
* created or updated.
|
||||
* @param Room The room for which the masked meshes should be created.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void CreateMaskMeshesForRoom(AMRUKRoom* Room);
|
||||
|
||||
/**
|
||||
* Remove mask meshes for the given room.
|
||||
* This function should only be executed when SpawnMode is set to None.
|
||||
* It only needs to be called after a room has been removed.
|
||||
* @param Room The room for which the masked meshes should be removed.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void RemoveMaskMeshesFromRoom(AMRUKRoom* Room);
|
||||
|
||||
/**
|
||||
* Return the captured distance map. Be sure to call CaptureDistanceMap() before
|
||||
* @return The captured distance map.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
UTexture* GetDistanceMap() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
UCanvasRenderTarget2D* GetDistanceMapRenderTarget() const;
|
||||
|
||||
/**
|
||||
* Retrieve the view info from the scene capture. This is useful for re projection of
|
||||
* the distance map in a material.
|
||||
* @return The view info.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
FMinimalViewInfo GetSceneCaptureView() const;
|
||||
|
||||
public:
|
||||
AMRUKDistanceMapGenerator();
|
||||
|
||||
protected:
|
||||
void BeginPlay() override;
|
||||
|
||||
private:
|
||||
TMap<AMRUKRoom*, TArray<AActor*>> SpawnedMaskMeshes;
|
||||
|
||||
int32 DistanceMapRT = -1;
|
||||
|
||||
UPROPERTY()
|
||||
class UMaterialInstanceDynamic* JFPassMaterialInstance = nullptr;
|
||||
|
||||
UPROPERTY()
|
||||
class UMaterialInstanceDynamic* DistanceMapFreeSpaceMaterialInstance = nullptr;
|
||||
UPROPERTY()
|
||||
class UMaterialInstanceDynamic* DistanceMapOccupiedSpaceMaterialInstance = nullptr;
|
||||
UPROPERTY()
|
||||
class UMaterialInstanceDynamic* DistanceMapAllSpaceMaterialInstance = nullptr;
|
||||
|
||||
UPROPERTY()
|
||||
UMaterialInterface* SceneObjectMaskMaterial;
|
||||
|
||||
UPROPERTY()
|
||||
UMaterialInterface* FloorMaskMaterial;
|
||||
|
||||
void CaptureInitialSceneMask();
|
||||
void RenderDistanceMap();
|
||||
|
||||
UFUNCTION()
|
||||
void OnRoomCreated(AMRUKRoom* Room);
|
||||
|
||||
UFUNCTION()
|
||||
void OnRoomUpdated(AMRUKRoom* Room);
|
||||
|
||||
UFUNCTION()
|
||||
AActor* CreateMaskMeshOfAnchor(AMRUKAnchor* Anchor);
|
||||
|
||||
UFUNCTION()
|
||||
AActor* UpdateMaskMeshOfAnchor(AMRUKAnchor* Anchor);
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Containers/Array.h"
|
||||
#include "Math/Vector2D.h"
|
||||
|
||||
MRUTILITYKIT_API void MRUKTriangulatePolygon(const TArray<TArray<FVector2f>>& Polygons, TArray<FVector2D>& Vertices, TArray<int32>& Indices);
|
||||
@@ -0,0 +1,191 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/SceneComponent.h"
|
||||
#include "MRUtilityKitGridSliceResizer.generated.h"
|
||||
|
||||
UENUM(BlueprintType, Meta = (Bitflags, UseEnumValuesAsMaskValuesInEditor = "true"))
|
||||
enum class EMRUKScaleCenterMode : uint8
|
||||
{
|
||||
None = 0 UMETA(Hidden),
|
||||
XAxis = 1,
|
||||
YAxis = 2,
|
||||
ZAxis = 4,
|
||||
};
|
||||
|
||||
/**
|
||||
* The GridSliceResizerComponent is a versatile tool designed to maintain the proportions of
|
||||
* specific areas of 3D meshes while allowing others to stretch during scaling. This component
|
||||
* should replace the static mesh component, rather than being used in conjunction with it.
|
||||
*
|
||||
* The concept of the GridSliceResizerComponent is similar to the popular 9-Slice-Scaling technique
|
||||
* used in 2D graphics, which keeps the borders of sprites unstretched while the inner rectangle is
|
||||
* stretched. In essence, the GridSliceResizerComponent is a 27-Slice-Scaler for 3D meshes.
|
||||
*
|
||||
* The component operates by dividing the bounding box of a 3D mesh into 27 cuboids, as illustrated below.
|
||||
* Not all cuboids are visible in this picture. Only the once that are front facing:
|
||||
*
|
||||
* +-----+-----------+-----+
|
||||
* /_____/___________/_____/|
|
||||
* /_____/___________/_____/||
|
||||
* / / / /|||
|
||||
* +-----+-----------+-----+ |||
|
||||
* | A | B | C |/|||
|
||||
* +-----+-----------+-----+ |||
|
||||
* | | | | |||
|
||||
* | D | E | F | |||
|
||||
* | | | |/||/
|
||||
* +-----+-----------+-----+ |/
|
||||
* | G | H | I | /
|
||||
* +-----+-----+-----+-----+
|
||||
*
|
||||
* The scaling behaviour is as follows (assuming all other faces of the bounding box are divided as the
|
||||
* front facing one):
|
||||
*
|
||||
* Center Cuboid (E): Vertices within this cuboid stretch on two axes (Y, Z).
|
||||
* Corner Cuboids (A, C, G, I): These cuboids do not stretch on any axis.
|
||||
* Middle Cuboids (B, H): These cuboids stretch horizontally but not vertically.
|
||||
* Middle Cuboids (D, F): These cuboids stretch vertically but not horizontally.
|
||||
*
|
||||
* The slicing areas are defined by the SlicerPivotOffset and BorderXNegative, BorderXPositive, etc.
|
||||
* These border values range from 0 to 1 and extend from the mesh's pivot (which may be offset by SlicerPivotOffset)
|
||||
* to the maximum or minimum of the bounding box's axis.
|
||||
* If all borders are set to 1, the mesh will stretch like a regular mesh during scaling. If set to 0, no stretching
|
||||
* will occur. Typically, you'll want the pivot in the middle of the mesh and the borders set to around 0.8.
|
||||
*
|
||||
* You can visualize the borders and pivot in the Actor editor preview using bDebugDrawPivot, bDebugDrawBorderX, etc.
|
||||
*
|
||||
* This component is only compatible with static meshes that have CPU access enabled. Ensure you enable CPU
|
||||
* access in the static mesh editor.
|
||||
*/
|
||||
UCLASS(ClassGroup = MRUtilityKit, Blueprintable, BlueprintType, meta = (BlueprintSpawnableComponent, DisplayName = "MR Utility Kit Grid Slice Resizer Component"))
|
||||
class MRUTILITYKIT_API UMRUKGridSliceResizerComponent : public USceneComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* The static mesh to slice. Make sure to enable CPU access on it.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
class UStaticMesh* Mesh;
|
||||
|
||||
/**
|
||||
* Slice border for the negative X axis.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit|Slices", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
double BorderXNegative = 1.0;
|
||||
|
||||
/**
|
||||
* Slice border for the positive X axis.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit|Slices", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
double BorderXPositive = 1.0;
|
||||
|
||||
/**
|
||||
* Slice border for the negative Y axis.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit|Slices", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
double BorderYNegative = 1.0;
|
||||
|
||||
/**
|
||||
* Slice border for the positive Y axis.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit|Slices", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
double BorderYPositive = 1.0;
|
||||
|
||||
/**
|
||||
* Slice border for the negative Z axis.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit|Slices", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
double BorderZNegative = 1.0;
|
||||
|
||||
/**
|
||||
* Slice border for the positive Z axis.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit|Slices", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
double BorderZPositive = 1.0;
|
||||
|
||||
/**
|
||||
* How much the meshes pivot should be offset when applying the slice borders.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
FVector SlicerPivotOffset;
|
||||
|
||||
/**
|
||||
* This parameter determines whether the center part of the object should be scaled.
|
||||
* If set to false, the center vertices will remain stationary. This is particularly useful when
|
||||
* you want to maintain the proportions of certain geometrical features in the center part, such
|
||||
* as a doorknob. By keeping the center vertices in place, you can avoid unwanted stretching effects,
|
||||
* resulting in a more visually appealing outcome.
|
||||
* However, it's important to note that for a convincing visual effect, the texture applied to the object should also not stretch.
|
||||
* If you encounter issues with texture stretching, consider adding an additional loop cut.
|
||||
* This can help maintain the texture's proportions and prevent it from distorting.
|
||||
* In case the mesh gets scaled down and some of the center vertices fall outside of the scaled down center
|
||||
* all vertices that are inside the center will be scaled down uniformly.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit", meta = (Bitmask, BitmaskEnum = "/Script/MRUtilityKit.EMRUKScaleCenterMode"))
|
||||
uint8 ScaleCenterMode = 0;
|
||||
|
||||
/**
|
||||
* Whether or not a collision mesh should be created for the static mesh.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
bool bGenerateCollision = true;
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
/**
|
||||
* Show the pivot of the mesh that gets used for the slice borders.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
bool bDebugDrawPivot = false;
|
||||
|
||||
/**
|
||||
* Show the slice borders on the X axis.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
bool bDebugDrawBorderX = false;
|
||||
|
||||
/**
|
||||
* Show the slice borders on the Y axis.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
bool bDebugDrawBorderY = false;
|
||||
|
||||
/**
|
||||
* Show the slice borders on the Z axis.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
bool bDebugDrawBorderZ = false;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Slice the mesh. This gets automatically called whenever
|
||||
* the scale of the owning Actor changes.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void SliceMesh();
|
||||
|
||||
public:
|
||||
UMRUKGridSliceResizerComponent();
|
||||
|
||||
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
virtual void OnRegister() override;
|
||||
|
||||
#if WITH_EDITOR
|
||||
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
friend class FMRUKGridSliceResizerSpec;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
class UProceduralMeshComponent* ProcMesh;
|
||||
|
||||
FVector ResizerScale = FVector::OneVector;
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MRUtilityKitRoom.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "ProceduralMeshComponent.h"
|
||||
#include "MRUtilityKitGuardian.generated.h"
|
||||
|
||||
/**
|
||||
* The Guardian is a procedural mesh that is generated from the anchor geometry and has the guardian material applied.
|
||||
* It is used to show the player where the walls and furniture. It prevents the player from walking into walls or furniture.
|
||||
* It uses TryGetClosestSurfacePosition to determine if the player is close to the walls or furniture.
|
||||
* This can be beneficial if your application has a full VR mode.
|
||||
*/
|
||||
UCLASS(ClassGroup = MRUtilityKit, meta = (DisplayName = "MR Utility Kit Guardian Actor"))
|
||||
class MRUTILITYKIT_API AMRUKGuardian : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Procedural mesh that is generated from the anchor geometry and has the guardian material applied.
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
TObjectPtr<UProceduralMeshComponent> GuardianMeshComponent;
|
||||
|
||||
/**
|
||||
* Attaches the procedural mesh component to this actor.
|
||||
* @param GuardianMesh The mesh to attach.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void CreateGuardian(UProceduralMeshComponent* GuardianMesh);
|
||||
|
||||
public:
|
||||
AMRUKGuardian(const FObjectInitializer& ObjectInitializer);
|
||||
};
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MRUtilityKit.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "MRUtilityKitGuardian.h"
|
||||
#include "MRUtilityKitGuardianSpawner.generated.h"
|
||||
|
||||
class AMRUKRoom;
|
||||
|
||||
/**
|
||||
* This class helps with spawning a guardian if the player gets close to any furniture or walls. This is useful if your application has a full VR mode.
|
||||
* It can spawn a guardian for each room in the scene. It can also spawn a guardian for the current room only.
|
||||
* For details about the guardian see the AMRUKGuardian class.
|
||||
*/
|
||||
UCLASS(ClassGroup = MRUtilityKit, meta = (DisplayName = "MR Utility Kit Guardian"))
|
||||
class MRUTILITYKIT_API AMRUKGuardianSpawner : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
AMRUKGuardianSpawner();
|
||||
|
||||
/**
|
||||
* Whether SpawnGuardian() should be called automatically after the mixed reality utility kit
|
||||
* has been initialized.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
EMRUKSpawnMode SpawnMode = EMRUKSpawnMode::CurrentRoomOnly;
|
||||
|
||||
/**
|
||||
* How close the camera needs to come to a surface before the guardian appears.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
double GuardianDistance = 0.75;
|
||||
|
||||
/**
|
||||
* Whether the fading value should be calculated for the shader or not.
|
||||
If fading is not needed this can save performance.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
bool EnableFade = true;
|
||||
|
||||
/**
|
||||
* Spawn the guardian. This will get called automatically after the mixed reality utility kit has
|
||||
* been initialized if SpawnMode is set to something other than None.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void SpawnGuardians(AMRUKRoom* Room);
|
||||
|
||||
/**
|
||||
* Set the guardian material to a different one.
|
||||
* @param Material The guardian material.
|
||||
*/
|
||||
UFUNCTION(BlueprintSetter, Category = "MR Utility Kit")
|
||||
void SetGuardianMaterial(UMaterialInstance* Material);
|
||||
|
||||
/**
|
||||
* Set the density of the grid.
|
||||
* @param Density The grid density.
|
||||
*/
|
||||
UFUNCTION(BlueprintSetter, Category = "MR Utility Kit")
|
||||
void SetGridDensity(double Density);
|
||||
|
||||
public:
|
||||
void Tick(float DeltaSeconds) override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* The material to use for the guardian. It needs to have a scalar parameter Fade
|
||||
* and a vector parameter WallScale. If this material is not set a default one
|
||||
* will be used.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintSetter = SetGuardianMaterial, Category = "MR Utility Kit")
|
||||
TObjectPtr<UMaterialInstance> GuardianMaterial = nullptr;
|
||||
|
||||
/**
|
||||
* How dense the grid should be.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintSetter = SetGridDensity, Category = "MR Utility Kit")
|
||||
double GridDensity = 2.0;
|
||||
|
||||
void BeginPlay() override;
|
||||
#if WITH_EDITOR
|
||||
void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
// Room UUID to spawned actors in this room
|
||||
TMap<AMRUKRoom*, TArray<AMRUKGuardian*>> SpawnedGuardians;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UMaterialInstanceDynamic> DynamicGuardianMaterial = nullptr;
|
||||
|
||||
UFUNCTION()
|
||||
void DestroyGuardians(AMRUKRoom* Room);
|
||||
|
||||
UFUNCTION()
|
||||
void OnRoomCreated(AMRUKRoom* Room);
|
||||
|
||||
UFUNCTION()
|
||||
void OnRoomUpdated(AMRUKRoom* Room);
|
||||
|
||||
UFUNCTION()
|
||||
void OnRoomRemoved(AMRUKRoom* Room);
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "MRUtilityKitLightDispatcher.generated.h"
|
||||
|
||||
/**
|
||||
* If you want to have highlights from lights over passthrough use this actor to collect all point lights in the scene and send them to the M_Highlights material.
|
||||
* It lights and sends them to a highlight material, which can be used to achieve highlights over Passthrough.
|
||||
* The highlight effect is achieved by using a material parameter collection.
|
||||
* See the PTRL Sample Project for an example of how to use this.
|
||||
*/
|
||||
UCLASS(ClassGroup = MRUtilityKit, meta = (DisplayName = "MR Utility Kit Light Dispatcher"))
|
||||
class MRUTILITYKIT_API AMRUKLightDispatcher : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* The material parameter collection in which to fill lights data.
|
||||
* This parameter collection gets then send to the shader.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
class UMaterialParameterCollection* Collection;
|
||||
|
||||
/**
|
||||
* Whether all point lights should be fetched automatically at BeginPlay().
|
||||
* The automatic fetching only works for PointLightActors. Actors that have PointLightComponents
|
||||
* attached to them will not be detected. These should be specified in AdditionalActorsToLookForPointLightComponents.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
bool ShouldFetchPointLightsAtBeginPlay = true;
|
||||
|
||||
/**
|
||||
* List of actor(s) that contain a PointLightComponent that should contribute to the highlight effect.
|
||||
* Use AddAdditionalPointLightActor to add actors during runtime.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
TArray<AActor*> AdditionalActorsToLookForPointLightComponents;
|
||||
|
||||
/**
|
||||
* PointLightActors to use for the highlight effect (not available if "Fetch Point Lights At Begin Play" is true).
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "!ShouldFetchPointLightsAtBeginPlay"), Category = "MR Utility Kit")
|
||||
TArray<class APointLight*> ManualPointLights;
|
||||
|
||||
/**
|
||||
* Add a actor to the AdditionalActorsToLookForPointLightComponents list.
|
||||
* This should be used during runtime instead of adding actors directly to AdditionalActorsToLookForPointLightComponents.
|
||||
* @param Actor Actor to add to AdditionalActorsToLookForPointLightComponents.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void AddAdditionalPointLightActor(AActor* Actor);
|
||||
|
||||
/**
|
||||
* Only callable in the editor from the scene, will update the linked parameter collection with the info
|
||||
* of the point lights in the scene (based on the parameters), updating the highlight effect in the process.
|
||||
* This is meant to preview the effect in the editor.
|
||||
*/
|
||||
UFUNCTION(CallInEditor, Category = "MR Utility Kit")
|
||||
void ForceUpdateCollection();
|
||||
|
||||
public:
|
||||
AMRUKLightDispatcher();
|
||||
|
||||
void Tick(float DeltaSeconds) override;
|
||||
|
||||
void FillParameterCollection();
|
||||
|
||||
protected:
|
||||
UPROPERTY(Transient)
|
||||
TArray<class UPointLightComponent*> PointLightComponents;
|
||||
|
||||
void BeginPlay() override;
|
||||
|
||||
void FillPointLights();
|
||||
void AddPointLightsFromActor(const AActor* Actor);
|
||||
};
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "MRUtilityKitRoom.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "MRUtilityKitPositionGenerator.generated.h"
|
||||
|
||||
/**
|
||||
* Holds the settings which are used for generating random positions. It offers several attributes to be configured, such as
|
||||
* which room to use, what actor to spawn, scene labels to use and much more. This struct is used by the position generator.
|
||||
* @see AMRUtilityKitPositionGenerator
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FMRUKRandomSpawnSettings
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* When the scene data is loaded, this controls what room(s) the position generator will be used in.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
EMRUKRoomFilter RoomFilter = EMRUKRoomFilter::CurrentRoomOnly;
|
||||
|
||||
/**
|
||||
* When an actor instance is reference here, this actor will be moved around.
|
||||
* If you'd need to spawn new actors, use ActorClass.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
AActor* ActorInstance = nullptr;
|
||||
|
||||
/**
|
||||
* Reference the specific actor class for spawning.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
TSubclassOf<AActor> ActorClass;
|
||||
|
||||
/**
|
||||
* How many instances to spawn at the random generated position per room.
|
||||
* Note: If using an ActorInstance this property is ignored
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
int SpawnAmount = 8;
|
||||
|
||||
/**
|
||||
* Maximum number of times to attempt spawning/moving an object before giving up.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
int MaxIterations = 1000;
|
||||
|
||||
/**
|
||||
* The type of surface by which to limit the generation.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
EMRUKSpawnLocation SpawnLocations = EMRUKSpawnLocation::Floating;
|
||||
|
||||
/**
|
||||
* The labels to include or exclude.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
FMRUKLabelFilter Labels;
|
||||
|
||||
/**
|
||||
* If enabled then the spawn position will be checked to make sure there is no overlap with physics colliders including themselves.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
bool CheckOverlaps = true;
|
||||
|
||||
/**
|
||||
* Required free space for the object.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
float OverrideBounds = -1;
|
||||
|
||||
/**
|
||||
* The CollisionChannel to use.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
TEnumAsByte<ECollisionChannel> CollisionChannel = ECC_WorldStatic;
|
||||
|
||||
/**
|
||||
* The clearance distance required in front of the surface in order for it to be considered a valid spawn position.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
float SurfaceClearanceDistance = 0.1f;
|
||||
};
|
||||
|
||||
/**
|
||||
* Position generator that can be used to generate random positions on the surface in a specific room or any room.
|
||||
*
|
||||
* It contains methods to generate random positions on the surface of a given spawn location,
|
||||
* while ensuring that the generated positions are at least `MinDistanceToEdge` away from any edges,
|
||||
* if it should run on start when MRUK initializes and follow the other settings specified in `SpawnSettings`.
|
||||
*/
|
||||
UCLASS()
|
||||
class MRUTILITYKIT_API AMRUtilityKitPositionGenerator : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
static bool CanSpawnBox(const UWorld* World, const FBox& Box, const FVector& SpawnPosition, const FQuat& SpawnRotation, const FCollisionQueryParams& QueryParams, ECollisionChannel CollisionChannel);
|
||||
|
||||
/**
|
||||
* Generates a set of random positions on the surface of a given spawn location, while ensuring that the generated positions
|
||||
* are at least `MinDistanceToEdge` away from any edges and follow the other settings specified in `SpawnSettings`.
|
||||
* @param OutTransforms An array of transforms representing the generated positions.
|
||||
* @return A boolean value indicating whether valid positions were found. If no valid positions could be found, `OutTransforms` will be empty.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool GenerateRandomPositionsOnSurface(TArray<FTransform>& OutTransforms);
|
||||
|
||||
/**
|
||||
* Generates a set of random positions on the surface of a given spawn location, while ensuring that the generated positions
|
||||
* are at least `MinDistanceToEdge` away from any edges and follow the other settings specified in `SpawnSettings` in the
|
||||
* give room.
|
||||
* @param Room The room where the positions should be generated in.
|
||||
* @param OutTransforms An array of transforms representing the generated positions.
|
||||
* @return A boolean value indicating whether valid positions were found. If no valid positions could be found, `OutTransforms` will be empty.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool GenerateRandomPositionsOnSurfaceInRoom(AMRUKRoom* Room, TArray<FTransform>& OutTransforms);
|
||||
|
||||
/**
|
||||
* Whether GenerateRandomPositionsOnSurface() should be called automatically after the mixed reality utility kit has been initialized
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
bool RunOnStart = true;
|
||||
|
||||
/**
|
||||
* Settings that should be used when generating random positions.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
FMRUKRandomSpawnSettings RandomSpawnSettings;
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
private:
|
||||
virtual UWorld* GetTickableGameObjectWorld() const { return GetWorld(); }
|
||||
|
||||
UFUNCTION()
|
||||
void SceneLoaded(bool Success);
|
||||
};
|
||||
@@ -0,0 +1,481 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "Dom/JsonObject.h"
|
||||
#include "MRUtilityKit.h"
|
||||
#include "OculusXRAnchorTypes.h"
|
||||
#include "MRUtilityKitRoom.generated.h"
|
||||
|
||||
class UMRUKRoomData;
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EMRUKSpawnLocation : uint8
|
||||
{
|
||||
Floating UMETA(DisplayName = "Floating"), // Spawn somewhere floating in the free space within the room
|
||||
AnySurface UMETA(DisplayName = "Any surface"), // Spawn on any surface (i.e. a combination of all 3 options below)
|
||||
VerticalSurfaces UMETA(DisplayName = "Vertical surfaces"), // Spawn only on vertical surfaces such as walls, windows, wall art, doors, etc...
|
||||
OnTopOfSurface UMETA(DisplayName = "On top of surfaces"), // Spawn on surfaces facing upwards such as ground, top of tables, beds, couches, etc...
|
||||
HangingDown UMETA(DisplayName = "Hanging down") // Spawn on surfaces facing downwards such as the ceiling
|
||||
};
|
||||
|
||||
enum class EMRUKBoxSide : uint8
|
||||
{
|
||||
XPos,
|
||||
XNeg,
|
||||
YPos,
|
||||
YNeg,
|
||||
ZPos,
|
||||
ZNeg,
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EMRUKRoomFilter : uint8
|
||||
{
|
||||
None,
|
||||
CurrentRoomOnly,
|
||||
AllRooms
|
||||
};
|
||||
|
||||
/**
|
||||
* Method to use when determining the position and rotation for the best pose.
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EMRUKPositioningMethod : uint8
|
||||
{
|
||||
/**
|
||||
* Center the object on the surface.
|
||||
*/
|
||||
Center = 0,
|
||||
/**
|
||||
* Snap the object to edge which is closest to the user.
|
||||
*/
|
||||
Edge,
|
||||
/**
|
||||
* Use the location where the ray hit the object as the location.
|
||||
* The rotation is dependent on the objects shape. For example for walls
|
||||
* the hit normal from the raycast will be used. For floors the rotation
|
||||
* will be towards the user and for volumes that got hit on the top the
|
||||
* rotation will be towards the longest edge that is nearest to the player.
|
||||
*/
|
||||
Default,
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents an anchor with its corresponding plane UVs in the Mixed Reality Utility Kit.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FMRUKAnchorWithPlaneUVs
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* A readonly reference to the anchor.
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
TObjectPtr<AMRUKAnchor> Anchor;
|
||||
|
||||
/**
|
||||
* An array of plane UVs that correspond to the anchor.
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
TArray<FMRUKPlaneUV> PlaneUVs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a room in the MRUK.
|
||||
* A room holds (MRUK)Anchors as children for entities such as Desk, Floor, Ceiling, Walls, etc. Those entities are defined with their label.
|
||||
* It also provides events which will be triggered when an anchor has been added, removed or updated from space setup.
|
||||
*
|
||||
* This room class calculates different helper properties such as Outline, Edges, Bounds
|
||||
* and provides room functions as helpers such as determine if a point in space (XYZ) is inside the room, generating points on surfaces, generate points in room (floating), raycasts and more.
|
||||
*/
|
||||
UCLASS(ClassGroup = MRUtilityKit, meta = (DisplayName = "MR Utility Kit Room Actor"))
|
||||
class MRUTILITYKIT_API AMRUKRoom : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAnchorUpdated, AMRUKAnchor*, Anchor);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAnchorCreated, AMRUKAnchor*, Anchor);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAnchorRemoved, AMRUKAnchor*, Anchor);
|
||||
|
||||
/**
|
||||
* The space handle of this anchor
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
FOculusXRUInt64 SpaceHandle;
|
||||
|
||||
/**
|
||||
* The anchors UUID
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
FOculusXRUUID AnchorUUID;
|
||||
|
||||
/**
|
||||
* Event that gets fired if a anchor in this room was updated.
|
||||
* E.g. volume or plane changed.
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
|
||||
FOnAnchorUpdated OnAnchorUpdated;
|
||||
|
||||
/**
|
||||
* Event that gets fired if a new anchor was created in this room.
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
|
||||
FOnAnchorCreated OnAnchorCreated;
|
||||
|
||||
/**
|
||||
* Event that gets fired if a anchor gets removed from this room.
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
|
||||
FOnAnchorRemoved OnAnchorRemoved;
|
||||
|
||||
/**
|
||||
* Bounds of the room.
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
FBox RoomBounds;
|
||||
|
||||
/**
|
||||
* Edges of the room.
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
TArray<FVector> RoomEdges;
|
||||
|
||||
/**
|
||||
* The floor anchor of this room.
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
TObjectPtr<AMRUKAnchor> FloorAnchor;
|
||||
|
||||
/**
|
||||
* The ceiling anchor of this room.
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
TObjectPtr<AMRUKAnchor> CeilingAnchor;
|
||||
|
||||
/**
|
||||
* The wall anchors of this room.
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
TArray<TObjectPtr<AMRUKAnchor>> WallAnchors;
|
||||
|
||||
/**
|
||||
* The global mesh anchor of this room.
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
TObjectPtr<AMRUKAnchor> GlobalMeshAnchor;
|
||||
|
||||
/**
|
||||
* All anchors which are possible to sit on.
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
TArray<TObjectPtr<AMRUKAnchor>> SeatAnchors;
|
||||
|
||||
/**
|
||||
* All anchors of this room.
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
TArray<TObjectPtr<AMRUKAnchor>> AllAnchors;
|
||||
|
||||
/**
|
||||
* Check whether the position is inside the room or not.
|
||||
* @param Position The position in world space to check.
|
||||
* @param TestVerticalBounds Whether the room should be constrained by vertical bounds or not in the check.
|
||||
* @return Whether the position is inside the room or not.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool IsPositionInRoom(const FVector& Position, bool TestVerticalBounds = true);
|
||||
|
||||
/**
|
||||
* Generate a uniform random position within the room.
|
||||
* @param OutPosition Contains the randomly generated position.
|
||||
* @param MinDistanceToSurface The minimum distance between the generated position and the closest surface/volume.
|
||||
* @param AvoidVolumes If true then the position will not be inside a volume and min distance away from it.
|
||||
* @return Return true if success otherwise false. If this fails it can be because the min distance to surface is too large.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool GenerateRandomPositionInRoom(FVector& OutPosition, float MinDistanceToSurface = 0.0f, bool AvoidVolumes = false);
|
||||
|
||||
/**
|
||||
* Generate a uniform random position within the room from a random stream.
|
||||
* @param OutPosition Contains the randomly generated position.
|
||||
* @param RandomStream A random generator used to generate the position on the plane.
|
||||
* @param MinDistanceToSurface The minimum distance between the generated position and the closest surface/volume.
|
||||
* @param AvoidVolumes If true then the position will not be inside a volume and min distance away from it.
|
||||
* @return Return true if success otherwise false. If this fails it can be because the min distance to surface is too large.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool GenerateRandomPositionInRoomFromStream(FVector& OutPosition, const FRandomStream& RandomStream, float MinDistanceToSurface = 0.0f, bool AvoidVolumes = false);
|
||||
|
||||
/**
|
||||
* Generates a random position on the surface of a given spawn location, while ensuring that the generated position is at least `MinDistanceToEdge` away from any edges. The `LabelFilter` parameter allows you to specify which types of surfaces should be considered for generating the random position.
|
||||
*
|
||||
* @param SpawnLocation The location where the random position should be generated.
|
||||
* @param MinDistanceToEdge The minimum distance from the edge that the generated position must have.
|
||||
* @param LabelFilter A filter that specifies which types of surfaces should be considered for generating the random position.
|
||||
* @param OutPosition The generated position.
|
||||
* @param OutNormal The normal vector of the generated position.
|
||||
* @return A boolean value indicating whether a valid position was found. If no valid position could be found, both `OutPosition` and `OutNormal` will be set to zero vectors.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool GenerateRandomPositionOnSurface(EMRUKSpawnLocation SpawnLocation, float MinDistanceToEdge, FMRUKLabelFilter LabelFilter, FVector& OutPosition, FVector& OutNormal);
|
||||
|
||||
/**
|
||||
* Cast a ray and return the closest hit anchor
|
||||
* @param Origin Origin The origin of the ray.
|
||||
* @param Direction Direction The direction of the ray.
|
||||
* @param MaxDist The maximum distance the ray should travel.
|
||||
* @param LabelFilter The label filter can be used to include/exclude certain labels from the search.
|
||||
* @param OutHit The closest hit.
|
||||
* @return The anchor that the ray hit.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (AutoCreateRefTerm = "LabelFilter"))
|
||||
AMRUKAnchor* Raycast(const FVector& Origin, const FVector& Direction, float MaxDist, const FMRUKLabelFilter& LabelFilter, FMRUKHit& OutHit);
|
||||
|
||||
/**
|
||||
* Cast a ray and collect hits against the volume and plane bounds in this room. The order of the hits in the array is not specified.
|
||||
* @param Origin Origin The origin of the ray.
|
||||
* @param Direction Direction The direction of the ray.
|
||||
* @param MaxDist The maximum distance the ray should travel.
|
||||
* @param OutHits The hits the ray collected.
|
||||
* @param LabelFilter The label filter can be used to include/exclude certain labels from the search.
|
||||
* @param OutAnchors The anchors that were hit. Each anchor in this array corresponds to a entry at the same position in OutHits.
|
||||
* @return Whether the ray hit anything
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (AutoCreateRefTerm = "LabelFilter"))
|
||||
bool RaycastAll(const FVector& Origin, const FVector& Direction, float MaxDist, const FMRUKLabelFilter& LabelFilter, TArray<FMRUKHit>& OutHits, TArray<AMRUKAnchor*>& OutAnchors);
|
||||
|
||||
/**
|
||||
* Clear all anchors from the room.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void ClearRoom();
|
||||
|
||||
/**
|
||||
* Check if the room does have any of the labels.
|
||||
* @param Labels The labels to check.
|
||||
* @return Whether the label was found in the room.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool DoesRoomHave(const TArray<FString>& Labels);
|
||||
|
||||
/**
|
||||
* Get the position on the surface that is closest to the given position with respect to the distance.
|
||||
* @param WorldPosition The position in world space from which the closest surface point should be found.
|
||||
* @param OutSurfacePosition The closest position on the closest surface if any. Otherwise zero.
|
||||
* @param OutSurfaceDistance The distance between WorldPosition and OutSurfacePosition.
|
||||
* @param LabelFilter The label filter can be used to include/exclude certain labels from the search.
|
||||
* @param MaxDistance The distance to which a closest surface position should be searched. Everything below or equal to zero will be treated as infinity.
|
||||
* @return The Anchor on which the closest surface position was found or a null pointer otherwise.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (AutoCreateRefTerm = "LabelFilter"))
|
||||
AMRUKAnchor* TryGetClosestSurfacePosition(const FVector& WorldPosition, FVector& OutSurfacePosition, double& OutSurfaceDistance, const FMRUKLabelFilter& LabelFilter, double MaxDistance = 0.0);
|
||||
|
||||
/**
|
||||
* Checks if the given position is on or inside of any scene volume in the room.
|
||||
* Floor, ceiling and wall anchors will be excluded from the search.
|
||||
* @param WorldPosition The position in world space to check
|
||||
* @param TestVerticalBounds Whether the vertical bounds should be checked or not
|
||||
* @param Tolerance Tolerance
|
||||
* @return The anchor the WorldPosition is in. A null pointer otherwise.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
AMRUKAnchor* IsPositionInSceneVolume(const FVector& WorldPosition, bool TestVerticalBounds = true, double Tolerance = 0.0);
|
||||
|
||||
/**
|
||||
* Finds the closest seat given a ray.
|
||||
* @param RayOrigin The origin of the ray.
|
||||
* @param RayDirection The direction of the ray.
|
||||
* @param OutSeatTransform The seat pose.
|
||||
* @return If any seat was found the Anchor that has seats available will be returned. Otherwise a null pointer.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
AMRUKAnchor* TryGetClosestSeatPose(const FVector& RayOrigin, const FVector& RayDirection, FTransform& OutSeatTransform);
|
||||
|
||||
/**
|
||||
* Finds all anchors in this room that have the given label attached.
|
||||
* @param Label The label to search for.
|
||||
* @return An array off anchors with the given label.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
TArray<AMRUKAnchor*> GetAnchorsByLabel(const FString& Label) const;
|
||||
|
||||
/**
|
||||
* Finds the first anchor in this room that has the given label attached.
|
||||
* @param Label The label to search for.
|
||||
* @return If found, the Anchor that has the label attached. Otherwise a null pointer.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
AMRUKAnchor* GetFirstAnchorByLabel(const FString& Label) const;
|
||||
|
||||
/**
|
||||
* Get a suggested pose (position & rotation) from a raycast to place objects on surfaces in the scene.
|
||||
* There are different positioning modes available. Default just uses the position where the raycast
|
||||
* hit the object. Edge snaps the position to the edge that is nearest to the user and Center simply
|
||||
* centers the position on top of the surface.
|
||||
* @param RayOrigin The origin of the ray.
|
||||
* @param RayDirection The direction of the ray.
|
||||
* @param MaxDist The maximum distance the ray should travel.
|
||||
* @param LabelFilter The label filter can be used to include/exclude certain labels from the search.
|
||||
* @param OutPose The calculated pose.
|
||||
* @param PositioningMethod The method that should be used for determining the position on the surface.
|
||||
* @return The anchor that was hit by the ray if any. Otherwise a null pointer.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (AutoCreateRefTerm = "LabelFilter"))
|
||||
AMRUKAnchor* GetBestPoseFromRaycast(const FVector& RayOrigin, const FVector& RayDirection, double MaxDist, const FMRUKLabelFilter& LabelFilter, FTransform& OutPose, EMRUKPositioningMethod PositioningMethod = EMRUKPositioningMethod::Default);
|
||||
|
||||
/**
|
||||
* Return the longest wall in the room that has no other walls behind it.
|
||||
* @param Tolerance The tolerance to use when determining wall that are behind.
|
||||
* @return The wall anchor that is the key wall in the room.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
AMRUKAnchor* GetKeyWall(double Tolerance = 0.1);
|
||||
|
||||
/**
|
||||
* Return the largest surface for a given label.
|
||||
* @param Label The label of the surfaces to search in.
|
||||
* @return The anchor that has the largest surface if any. Otherwise, a null pointer.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
AMRUKAnchor* GetLargestSurface(const FString& Label);
|
||||
|
||||
/**
|
||||
* Attach a procedural mesh to the walls. This is done at the room level to ensure the UV coordinates
|
||||
* can be done in a seamless way if desired.
|
||||
* @param WallTextureCoordinateModes Mode of the wall texture coordinates.
|
||||
* @param CutHoleLabels Labels for which holes should be cut into the plane meshes
|
||||
* @param ProceduralMaterial Material to apply on top of the procedural mesh.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (AutoCreateRefTerm = "WallTextureCoordinateModes", DeprecatedFunction, DeprecationMessage = "Use GenerateProceduralMesh instead."))
|
||||
void AttachProceduralMeshToWalls(const TArray<FMRUKTexCoordModes>& WallTextureCoordinateModes, const TArray<FString>& CutHoleLabels, UMaterialInterface* ProceduralMaterial = nullptr);
|
||||
|
||||
/**
|
||||
* Spawn meshes on the position of the anchors of the room.
|
||||
* The actors should have Z as up Y as right and X as forward.
|
||||
* The pivot point should be in the bottom center.
|
||||
* @param SpawnGroups A map which tells to spawn which actor to a given label.
|
||||
* @param CutHoleLabels Labels for which the generated mesh should have holes. Only works with planes.
|
||||
* @param ProceduralMaterial Material to apply on top of the procedural mesh if any.
|
||||
* @param ShouldFallbackToProcedural Whether or not it should by default fallback to generating a procedural mesh if no actor class has been specified for a label.
|
||||
* @return All spawned interior actors.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DeprecatedFunction, DeprecationMessage = "Use AMRUKAnchorActorSpawner instead."), Category = "MR Utility Kit")
|
||||
TArray<AActor*> SpawnInterior(const TMap<FString, FMRUKSpawnGroup>& SpawnGroups, const TArray<FString>& CutHoleLabels, UMaterialInterface* ProceduralMaterial = nullptr, bool ShouldFallbackToProcedural = true);
|
||||
|
||||
/**
|
||||
* Spawn meshes on the position of the anchors of the room from a random stream.
|
||||
* The actors should have Z as up Y as right and X as forward.
|
||||
* The pivot point should be in the bottom center.
|
||||
* @param SpawnGroups A map wich tells to spawn which actor to a given label.
|
||||
* @param CutHoleLabels Labels for which the generated mesh should have holes. Only works with planes.
|
||||
* @param RandomStream A random generator to choose randomly between actor classes if there a multiple for one label.
|
||||
* @param ProceduralMaterial Material to apply on top of the procedural mesh if any.
|
||||
* @param ShouldFallbackToProcedural Whether or not it should by default fallback to generating a procedural mesh if no actor class has been specified for a label.
|
||||
* @return All spawned interior actors.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DeprecatedFunction, DeprecationMessage = "Use AMRUKAnchorActorSpawner instead."), Category = "MR Utility Kit")
|
||||
TArray<AActor*> SpawnInteriorFromStream(const TMap<FString, FMRUKSpawnGroup>& SpawnGroups, const FRandomStream& RandomStream, const TArray<FString>& CutHoleLabels, UMaterialInterface* ProceduralMaterial = nullptr, bool ShouldFallbackToProcedural = true);
|
||||
|
||||
/**
|
||||
* Check if the given anchor is a wall anchor.
|
||||
* @param Anchor The anchor to check.
|
||||
* @return Whether the anchor is a wall anchor or not.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool IsWallAnchor(AMRUKAnchor* Anchor) const;
|
||||
|
||||
/**
|
||||
* Compute the wall mesh texture coordinate adjustments that are needed to generate proper texture coordinates for the walls.
|
||||
* @param WallTextureCoordinateModes The texture coordinate mode to use for the walls.
|
||||
* @param OutAnchorsWithPlaneUVs The computed texture coordinate adjustment with the wall anchor.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void ComputeWallMeshUVAdjustments(const TArray<FMRUKTexCoordModes>& WallTextureCoordinateModes, TArray<FMRUKAnchorWithPlaneUVs>& OutAnchorsWithPlaneUVs);
|
||||
|
||||
/**
|
||||
* Load the triangle mesh of the global mesh anchor if it's available.
|
||||
* @param Material The Material to show if the global mesh is visible.
|
||||
* @return On success true, otherwise false.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool LoadGlobalMeshFromDevice(UMaterialInterface* Material = nullptr);
|
||||
|
||||
/**
|
||||
* Load the triangle mesh of the global mesh anchor. For this function to succeed you need to make
|
||||
* sure to have a global mesh specified in the JSON file. Not every JSON file has a global mesh in it.
|
||||
* @param JsonString The string with the JSON data.
|
||||
* @param Material Material to apply on the global mesh.
|
||||
* @return On Success true, otherwise false.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool LoadGlobalMeshFromJsonString(const FString& JsonString, UMaterialInterface* Material = nullptr);
|
||||
|
||||
/**
|
||||
* Compute the centroid of the room by taking the points of the floor boundary.
|
||||
* The centroid may be outside of the room for non convex rooms.
|
||||
* The Z value determines the height of the resulting vectors and ranges from
|
||||
* 0 to 1. A Z value of 1 corresponds to the ceiling positions Z, while a Z value
|
||||
* of 0 corresponds to the floor positions Z. Any value between 0 and 1 will
|
||||
* interpolate between the two values.
|
||||
* In case the floor and ceiling anchors haven't been loaded yet a zero vector
|
||||
* will be returned.
|
||||
* @param Z Value used for interpolation of Z.
|
||||
* @return The centroid.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
FVector ComputeCentroid(double Z = 0.5);
|
||||
|
||||
public:
|
||||
AMRUKRoom(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
void EndPlay(EEndPlayReason::Type Reason) override;
|
||||
|
||||
void LoadFromData(UMRUKRoomData* RoomData);
|
||||
|
||||
void AttachProceduralMeshToWalls(const TArray<FString>& CutHoleLabels, UMaterialInterface* ProceduralMaterial = nullptr);
|
||||
void UpdateWorldLock(APawn* Pawn, const FVector& HeadWorldPosition) const;
|
||||
|
||||
TSharedRef<FJsonObject> JsonSerialize();
|
||||
|
||||
bool Corresponds(UMRUKRoomData* RoomQuery) const;
|
||||
|
||||
private:
|
||||
friend class FMRUKSpec;
|
||||
|
||||
AMRUKAnchor* SpawnAnchor();
|
||||
|
||||
void InitializeRoom();
|
||||
void ComputeRoomBounds();
|
||||
void ComputeAnchorHierarchy();
|
||||
void ComputeSeats();
|
||||
void ComputeRoomEdges();
|
||||
|
||||
UFUNCTION(CallInEditor)
|
||||
void AddAnchorToRoom(AMRUKAnchor* Anchor);
|
||||
|
||||
class UProceduralMeshComponent* GetOrCreateGlobalMeshProceduralMeshComponent(bool& OutExistedAlready) const;
|
||||
void SetupGlobalMeshProceduralMeshComponent(UProceduralMeshComponent& ProcMeshComponent, bool ExistedAlready, UMaterialInterface* Material) const;
|
||||
|
||||
/**
|
||||
* Get the list of walls in an order such that each one wall shares an edge with the next
|
||||
* one in the list.
|
||||
*/
|
||||
TArray<TObjectPtr<AMRUKAnchor>> ComputeConnectedWalls() const;
|
||||
|
||||
FOculusXRRoomLayout RoomLayout;
|
||||
UPROPERTY()
|
||||
AMRUKAnchor* KeyWallAnchor = nullptr;
|
||||
|
||||
struct Surface
|
||||
{
|
||||
AMRUKAnchor* Anchor;
|
||||
float UsableArea;
|
||||
bool IsPlane;
|
||||
FBox2D Bounds;
|
||||
EMRUKBoxSide Side;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "Engine/DataTable.h"
|
||||
#include "MRUtilityKitSceneDataProvider.generated.h"
|
||||
|
||||
UCLASS(ClassGroup = MRUtilityKit, meta = (DisplayName = "MR Utility Kit Scene Data Provider"))
|
||||
/*
|
||||
* This actor is used to provide scene data to the MR Utility Kit when running in editor.
|
||||
* You can also use it to not load a room from device.
|
||||
* Use RandomRoom to load a random room from the list of rooms.
|
||||
*/
|
||||
class MRUTILITYKIT_API AMRUKSceneDataProvider : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/*
|
||||
* This list holds the rooms that can be loaded, the key is the room type and the value is a data table that contains multiple rooms.
|
||||
* Roomtypes such as Bedrooms, Livingrooms, etc.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
|
||||
TMap<FString, UDataTable*> Rooms;
|
||||
|
||||
/*
|
||||
* When this is true, a random room will be loaded from the list of rooms.
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "MR Utility Kit")
|
||||
bool bUseRandomRoom = true;
|
||||
|
||||
/*
|
||||
* When this is true, a random room will be loaded a specific room class, defined in Rooms (Bedrooms, Offices, ..).
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "MR Utility Kit", meta = (EditCondition = "!bUseRandomRoom", EditConditionHides))
|
||||
bool bUseRandomRoomFromClass = false;
|
||||
|
||||
/*
|
||||
* Use this property to define a specific room class to load, only visible when bUseRandomRoomFromClass is true.
|
||||
* This can be a room class such as Bedrooms, Offices, ..
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "MR Utility Kit", meta = (EditCondition = "bUseRandomRoomFromClass && !bUseRandomRoom", EditConditionHides))
|
||||
FString SpecificRoomClass;
|
||||
|
||||
/*
|
||||
* Define a specific room to load, only visible when bUseRandomRoom is false.
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "MR Utility Kit", meta = (EditCondition = "!bUseRandomRoom && !bUseRandomRoomFromClass", EditConditionHides))
|
||||
FString SpecificRoomName;
|
||||
|
||||
/*
|
||||
* Gets you a room from the list of rooms, if bUseRandomRoom is true, a random room will be returned.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void GetRoom(FString& RoomJSON, FString& RoomName);
|
||||
|
||||
protected:
|
||||
// Called when the game starts or when spawned
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
public:
|
||||
// Called every frame
|
||||
virtual void Tick(float DeltaTime) override;
|
||||
};
|
||||
|
||||
USTRUCT(Blueprintable, BlueprintType)
|
||||
struct FJSONData : public FTableRowBase
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "MR Utility Kit")
|
||||
FString JSON;
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "MRUtilityKitSeatsComponent.generated.h"
|
||||
|
||||
/**
|
||||
* This component gets attached to Anchors which have seats available.
|
||||
* Seats can be used for example to spawn avatars in the correct locations.
|
||||
*/
|
||||
UCLASS(ClassGroup = MRUtilityKit)
|
||||
class MRUTILITYKIT_API UMRUKSeatsComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
TArray<FTransform> SeatPoses;
|
||||
|
||||
/**
|
||||
* Calculate the seats poses that are available on the actor.
|
||||
* This gets called automatically after the room has been loaded.
|
||||
* However, it's okay to call this function again with a different SeatWidth.
|
||||
* The seat poses will then get recalculated.
|
||||
* @param SeatWidth The width of each seat.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void CalculateSeatPoses(double SeatWidth = 60.0);
|
||||
};
|
||||
@@ -0,0 +1,204 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Dom/JsonObject.h"
|
||||
#include "OculusXRAnchorTypes.h"
|
||||
#include "OculusXRRoomLayoutManagerComponent.h"
|
||||
|
||||
TSharedPtr<FJsonValue> MRUKSerialize(const FString& String);
|
||||
|
||||
void MRUKDeserialize(const FJsonValue& Value, FString& String);
|
||||
|
||||
TSharedPtr<FJsonValue> MRUKSerialize(const FOculusXRUUID& UUID);
|
||||
|
||||
void MRUKDeserialize(const FJsonValue& Value, FOculusXRUUID& UUID);
|
||||
|
||||
TSharedPtr<FJsonValue> MRUKSerialize(const double& Number);
|
||||
|
||||
void MRUKDeserialize(const FJsonValue& Value, double& Number);
|
||||
|
||||
TSharedPtr<FJsonValue> MRUKSerialize(const FOculusXRRoomLayout& RoomLayout);
|
||||
|
||||
void MRUKDeserialize(const FJsonValue& Value, FOculusXRRoomLayout& RoomLayout);
|
||||
|
||||
template <typename T>
|
||||
TSharedPtr<FJsonValue> MRUKSerialize(const UE::Math::TVector2<T>& Vector)
|
||||
{
|
||||
return MakeShareable(new FJsonValueArray({ MakeShareable(new FJsonValueNumber(Vector.X)), MakeShareable(new FJsonValueNumber(Vector.Y)) }));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void MRUKDeserialize(const FJsonValue& Value, UE::Math::TVector2<T>& Vector)
|
||||
{
|
||||
if (auto Array = Value.AsArray(); Array.Num() == 2)
|
||||
{
|
||||
MRUKDeserialize(*Array[0], Vector.X);
|
||||
MRUKDeserialize(*Array[1], Vector.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogJson, Error, TEXT("Json Array is of length %d (expected 2) when deserializing TVector2"), Array.Num());
|
||||
Vector = UE::Math::TVector2<T>::ZeroVector;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
TSharedPtr<FJsonValue> MRUKSerialize(const UE::Math::TVector<T>& Vector)
|
||||
{
|
||||
return MakeShareable(new FJsonValueArray({ MakeShareable(new FJsonValueNumber(Vector.X)), MakeShareable(new FJsonValueNumber(Vector.Y)), MakeShareable(new FJsonValueNumber(Vector.Z)) }));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void MRUKDeserialize(const FJsonValue& Value, UE::Math::TVector<T>& Vector)
|
||||
{
|
||||
auto Array = Value.AsArray();
|
||||
if (Array.Num() == 3)
|
||||
{
|
||||
MRUKDeserialize(*Array[0], Vector.X);
|
||||
MRUKDeserialize(*Array[1], Vector.Y);
|
||||
MRUKDeserialize(*Array[2], Vector.Z);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogJson, Error, TEXT("Json Array is of length %d (expected 3) when deserializing TVector"), Array.Num());
|
||||
Vector = UE::Math::TVector<T>::ZeroVector;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
TSharedPtr<FJsonValue> MRUKSerialize(const UE::Math::TRotator<T>& Rotation)
|
||||
{
|
||||
return MakeShareable(new FJsonValueArray({ MakeShareable(new FJsonValueNumber(Rotation.Pitch)), MakeShareable(new FJsonValueNumber(Rotation.Yaw)), MakeShareable(new FJsonValueNumber(Rotation.Roll)) }));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void MRUKDeserialize(const FJsonValue& Value, UE::Math::TRotator<T>& Rotation)
|
||||
{
|
||||
auto Array = Value.AsArray();
|
||||
if (Array.Num() == 3)
|
||||
{
|
||||
MRUKDeserialize(*Array[0], Rotation.Pitch);
|
||||
MRUKDeserialize(*Array[1], Rotation.Yaw);
|
||||
MRUKDeserialize(*Array[2], Rotation.Roll);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogJson, Error, TEXT("Json Array is of length %d (expected 3) when deserializing TRotator"), Array.Num());
|
||||
Rotation = UE::Math::TRotator<T>::ZeroRotator;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
TSharedPtr<FJsonValue> MRUKSerialize(const UE::Math::TBox2<T>& Box)
|
||||
{
|
||||
if (Box.bIsValid)
|
||||
{
|
||||
const TSharedRef<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
|
||||
JsonObject->SetField(TEXT("Min"), MRUKSerialize(Box.Min));
|
||||
JsonObject->SetField(TEXT("Max"), MRUKSerialize(Box.Max));
|
||||
return MakeShareable(new FJsonValueObject(JsonObject));
|
||||
}
|
||||
else
|
||||
{
|
||||
return MakeShareable(new FJsonValueNull());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void MRUKDeserialize(const FJsonValue& Value, UE::Math::TBox2<T>& Box)
|
||||
{
|
||||
if (Value.IsNull())
|
||||
{
|
||||
Box.Init();
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto Object = Value.AsObject();
|
||||
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("Min")), Box.Min);
|
||||
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("Max")), Box.Max);
|
||||
Box.bIsValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
TSharedPtr<FJsonValue> MRUKSerialize(const UE::Math::TBox<T>& Box)
|
||||
{
|
||||
if (Box.IsValid)
|
||||
{
|
||||
const TSharedRef<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
|
||||
JsonObject->SetField(TEXT("Min"), MRUKSerialize(Box.Min));
|
||||
JsonObject->SetField(TEXT("Max"), MRUKSerialize(Box.Max));
|
||||
return MakeShareable(new FJsonValueObject(JsonObject));
|
||||
}
|
||||
else
|
||||
{
|
||||
return MakeShareable(new FJsonValueNull());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void MRUKDeserialize(const FJsonValue& Value, UE::Math::TBox<T>& Box)
|
||||
{
|
||||
if (Value.IsNull())
|
||||
{
|
||||
Box.Init();
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto Object = Value.AsObject();
|
||||
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("Min")), Box.Min);
|
||||
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("Max")), Box.Max);
|
||||
Box.IsValid = 1;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
TSharedPtr<FJsonValue> MRUKSerialize(const UE::Math::TTransform<T>& Transform)
|
||||
{
|
||||
const TSharedRef<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
|
||||
JsonObject->SetField(TEXT("Translation"), MRUKSerialize(Transform.GetTranslation()));
|
||||
JsonObject->SetField(TEXT("Rotation"), MRUKSerialize(Transform.Rotator()));
|
||||
JsonObject->SetField(TEXT("Scale"), MRUKSerialize(Transform.GetScale3D()));
|
||||
return MakeShareable(new FJsonValueObject(JsonObject));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void MRUKDeserialize(const FJsonValue& Value, UE::Math::TTransform<T>& Transform)
|
||||
{
|
||||
const auto Object = Value.AsObject();
|
||||
UE::Math::TVector<T> Translation;
|
||||
UE::Math::TRotator<T> Rotation;
|
||||
UE::Math::TVector<T> Scale;
|
||||
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("Translation")), Translation);
|
||||
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("Rotation")), Rotation);
|
||||
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("Scale")), Scale);
|
||||
|
||||
Transform.SetComponents(UE::Math::TQuat<T>(Rotation), Translation, Scale);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
TSharedPtr<FJsonValue> MRUKSerialize(const TArray<T>& Array)
|
||||
{
|
||||
TArray<TSharedPtr<FJsonValue>> JsonArray;
|
||||
JsonArray.Reserve(Array.Num());
|
||||
for (const auto& Item : Array)
|
||||
{
|
||||
JsonArray.Add(MRUKSerialize(Item));
|
||||
}
|
||||
return MakeShareable(new FJsonValueArray(JsonArray));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void MRUKDeserialize(const FJsonValue& Value, TArray<T>& OutArray)
|
||||
{
|
||||
auto Array = Value.AsArray();
|
||||
OutArray.Empty();
|
||||
OutArray.Reserve(Array.Num());
|
||||
for (const auto& Item : Array)
|
||||
{
|
||||
T ItemDeserialized;
|
||||
MRUKDeserialize(*Item, ItemDeserialized);
|
||||
OutArray.Push(ItemDeserialized);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Dom/JsonObject.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "GameFramework/WorldSettings.h"
|
||||
#include "MRUtilityKitRoom.h"
|
||||
#include "MRUtilityKit.h"
|
||||
#include "MRUtilityKitData.h"
|
||||
#include "OculusXRAnchorsRequests.h"
|
||||
#include "Subsystems/GameInstanceSubsystem.h"
|
||||
#include "Tickable.h"
|
||||
|
||||
#include "MRUtilityKitSubsystem.generated.h"
|
||||
|
||||
/**
|
||||
* The Mixed Reality Utility Kit subsystem.
|
||||
*
|
||||
* This subsystem acts as a container for scene/anchor data. It has methods to load
|
||||
* the scene data from the device or a JSON file. After the scene data has been loaded
|
||||
* it will be stored inside the subsystem to make it possible to query the data from
|
||||
* everywhere. In addition, it offers methods to fulfill queries on the scene data
|
||||
* like ray casts or simple content placement.
|
||||
*
|
||||
* The subsystem only contains core functionality that is useful for most cases.
|
||||
* More specific functionality is part of actors. For example, if your goal is to spawn
|
||||
* meshes in the place of scene anchors you can place the AMRUKAnchorActorSpawner in the
|
||||
* level to do this. When a level loads you would first load the anchor data from the
|
||||
* device with this subsystem by calling LoadSceneFromDevice() and then the AMRUKAnchorActorSpawner
|
||||
* will listen for the subsystem to load the scene data and then spawn the actors accordingly.
|
||||
*
|
||||
* You can expect methods in this subsystem to take all loaded rooms into consideration when computing.
|
||||
* If you want to use a method only on a single specific room, there is most of the time a method
|
||||
* with the same name on the AMRUKRoom.
|
||||
*/
|
||||
UCLASS(ClassGroup = MRUtilityKit)
|
||||
class MRUTILITYKIT_API UMRUKSubsystem : public UGameInstanceSubsystem, public FTickableGameObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnLoaded, bool, Success);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCaptureComplete, bool, Success);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRoomCreated, AMRUKRoom*, Room);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRoomUpdated, AMRUKRoom*, Room);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRoomRemoved, AMRUKRoom*, Room);
|
||||
|
||||
/**
|
||||
* The status of the scene loading. When loading from device this is an asynchronous process
|
||||
* so will be in the Busy state until it moves to Complete or Failed.
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
EMRUKInitStatus SceneLoadStatus = EMRUKInitStatus::None;
|
||||
|
||||
/**
|
||||
* An event that will trigger when a scene is loaded either from Device or from JSON.
|
||||
* The Success parameter indicates whether the scene was loaded successfully or not.
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
|
||||
FOnLoaded OnSceneLoaded;
|
||||
|
||||
/**
|
||||
* An event that gets fired after a room has been created.
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
|
||||
FOnRoomCreated OnRoomCreated;
|
||||
|
||||
/**
|
||||
* An event that gets fired after a room has been updated.
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
|
||||
FOnRoomUpdated OnRoomUpdated;
|
||||
|
||||
/**
|
||||
* An event that gets fired when a room gets removed.
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
|
||||
FOnRoomRemoved OnRoomRemoved;
|
||||
|
||||
/**
|
||||
* An event that will trigger when the capture flow completed.
|
||||
* The Success parameter indicates whether the scene was captured successfully or not.
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
|
||||
FOnCaptureComplete OnCaptureComplete;
|
||||
|
||||
/**
|
||||
* Contains a list of rooms that are tracked by the mixed reality utility kit subsystem.
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
||||
TArray<TObjectPtr<AMRUKRoom>> Rooms;
|
||||
|
||||
/**
|
||||
* When world locking is enabled the position of the VR Pawn will be adjusted each frame to ensure
|
||||
* the room anchors are where they should be relative to the camera position. This is necessary to
|
||||
* ensure the position of the virtual objects in the world do not get out of sync with the real world.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
||||
bool EnableWorldLock = true;
|
||||
|
||||
|
||||
/**
|
||||
* Cast a ray and return the closest hit anchor in the scene.
|
||||
* @param Origin Origin The origin of the ray.
|
||||
* @param Direction Direction The direction of the ray.
|
||||
* @param MaxDist The maximum distance the ray should travel.
|
||||
* @param LabelFilter The label filter can be used to include/exclude certain labels from the search.
|
||||
* @param OutHit The closest hit.
|
||||
* @return The anchor that the ray hit
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (AutoCreateRefTerm = "LabelFilter"))
|
||||
AMRUKAnchor* Raycast(const FVector& Origin, const FVector& Direction, float MaxDist, const FMRUKLabelFilter& LabelFilter, FMRUKHit& OutHit);
|
||||
|
||||
/**
|
||||
* Cast a ray and collect hits against the volumes and plane bounds in every room in the scene.
|
||||
* The order of the hits in the array is not specified.
|
||||
* @param Origin Origin The origin of the ray.
|
||||
* @param Direction Direction The direction of the ray.
|
||||
* @param MaxDist The maximum distance the ray should travel.
|
||||
* @param LabelFilter The label filter can be used to include/exclude certain labels from the search.
|
||||
* @param OutHits The hits the ray collected.
|
||||
* @param OutAnchors The anchors that were hit. Each anchor in this array corresponds to a entry at the same position in OutHits.
|
||||
* @return Whether the ray hit anything
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (AutoCreateRefTerm = "LabelFilter"))
|
||||
bool RaycastAll(const FVector& Origin, const FVector& Direction, float MaxDist, const FMRUKLabelFilter& LabelFilter, TArray<FMRUKHit>& OutHits, TArray<AMRUKAnchor*>& OutAnchors);
|
||||
|
||||
/**
|
||||
* Return the room that the headset is currently in. If the headset is not in any given room
|
||||
* then it will return the room the headset was last in when this function was called.
|
||||
* If the headset hasn't been in a valid room yet then return the first room in the list.
|
||||
* If no rooms have been loaded yet then return null.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
AMRUKRoom* GetCurrentRoom() const;
|
||||
|
||||
/**
|
||||
* Save all rooms and anchors to JSON. This JSON representation can than later be used by
|
||||
* LoadSceneFromJsonString() to load the scene again.
|
||||
* @return the JSON string.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
FString SaveSceneToJsonString();
|
||||
|
||||
/**
|
||||
* Load rooms and anchors from a JSON representation.
|
||||
* If the scene is already loaded the scene will be updated with the changes.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void LoadSceneFromJsonString(const FString& String);
|
||||
|
||||
/**
|
||||
* Load rooms and anchors from the device.
|
||||
* If the scene is already loaded the scene will be updated with the changes.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void LoadSceneFromDevice();
|
||||
|
||||
|
||||
/**
|
||||
* Removes and clears every room.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
void ClearScene();
|
||||
|
||||
/**
|
||||
* Get the position on the surface that is closest to the given position with respect to the distance in all rooms.
|
||||
* @param WorldPosition The position in world space from which the closest surface point should be found.
|
||||
* @param OutSurfacePosition The closest position on the closest surface if any. Otherwise zero.
|
||||
* @param LabelFilter The label filter can be used to include/exclude certain labels from the search.
|
||||
* @param MaxDistance The distance to which a closest surface position should be searched. Everything below or equal to zero will be treated as infinity.
|
||||
* @return The Anchor on which the closest surface position was found or a null pointer otherwise.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (AutoCreateRefTerm = "LabelFilter"))
|
||||
AMRUKAnchor* TryGetClosestSurfacePosition(const FVector& WorldPosition, FVector& OutSurfacePosition, const FMRUKLabelFilter& LabelFilter, double MaxDistance = 0.0);
|
||||
|
||||
/**
|
||||
* Finds the closest seat given a ray.
|
||||
* @param RayOrigin The origin of the ray.
|
||||
* @param RayDirection The direction of the ray.
|
||||
* @param OutSeatTransform The seat pose.
|
||||
* @return If any seat was found the Anchor that has seats available will be returned. Otherwise a null pointer.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
AMRUKAnchor* TryGetClosestSeatPose(const FVector& RayOrigin, const FVector& RayDirection, FTransform& OutSeatTransform);
|
||||
|
||||
/**
|
||||
* Get a suggested pose (position & rotation) from a raycast to place objects on surfaces in the scene.
|
||||
* There are different positioning modes available. Default just uses the position where the raycast
|
||||
* hit the object. Edge snaps the position to the edge that is nearest to the user and Center simply
|
||||
* centers the position on top of the surface.
|
||||
* @param RayOrigin The origin of the ray.
|
||||
* @param RayDirection The direction of the ray.
|
||||
* @param MaxDist The maximum distance the ray should travel.
|
||||
* @param LabelFilter The label filter can be used to include/exclude certain labels from the search.
|
||||
* @param OutPose The calculated pose.
|
||||
* @param PositioningMethod The method that should be used for determining the position on the surface.
|
||||
* @return The anchor that was hit by the ray if any. Otherwise a null pointer.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (AutoCreateRefTerm = "LabelFilter"))
|
||||
AMRUKAnchor* GetBestPoseFromRaycast(const FVector& RayOrigin, const FVector& RayDirection, double MaxDist, const FMRUKLabelFilter& LabelFilter, FTransform& OutPose, EMRUKPositioningMethod PositioningMethod = EMRUKPositioningMethod::Default);
|
||||
|
||||
/**
|
||||
* Return the longest wall in the current room that has no other walls behind it.
|
||||
* @param Tolerance The tolerance to use when determining wall that are behind.
|
||||
* @return The wall anchor that is the key wall in the room.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
AMRUKAnchor* GetKeyWall(double Tolerance = 0.1);
|
||||
|
||||
/**
|
||||
* Return the largest surface for a given label in the current room.
|
||||
* @param Label The label of the surfaces to search in.
|
||||
* @return The anchor that has the largest surface if any. Otherwise, a null pointer.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
AMRUKAnchor* GetLargestSurface(const FString& Label);
|
||||
|
||||
/**
|
||||
* Checks if the given position is on or inside of any scene volume in the rooms.
|
||||
* All rooms will be checked and the first anchors scene volume that has the point on or inside it will be returned.
|
||||
* @param WorldPosition The position in world space to check
|
||||
* @param TestVerticalBounds Whether the vertical bounds should be checked or not
|
||||
* @param Tolerance Tolerance
|
||||
* @return The anchor the WorldPosition is in. A null pointer otherwise.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
AMRUKAnchor* IsPositionInSceneVolume(const FVector& WorldPosition, bool TestVerticalBounds = true, double Tolerance = 0.0);
|
||||
|
||||
/**
|
||||
* Spawn meshes on the position of the anchors of each room.
|
||||
* The actors should have Z as up Y as right and X as forward.
|
||||
* The pivot point should be in the bottom center.
|
||||
* @param SpawnGroups A map which tells to spawn which actor to a given label.
|
||||
* @param ProceduralMaterial Material to apply on top of the procedural mesh if any.
|
||||
* @param CutHoleLabels Labels for which the generated mesh should have holes. Only works with planes.
|
||||
* @param ShouldFallbackToProcedural Whether or not it should by default fallback to generating a procedural mesh if no actor class has been specified for a label.
|
||||
* @return The spawned actors.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DeprecatedFunction, DeprecationMessage = "Use AMRUKAnchorActorSpawner instead."), Category = "MR Utility Kit")
|
||||
TArray<AActor*> SpawnInterior(const TMap<FString, FMRUKSpawnGroup>& SpawnGroups, const TArray<FString>& CutHoleLabels, UMaterialInterface* ProceduralMaterial = nullptr, bool ShouldFallbackToProcedural = true);
|
||||
|
||||
/**
|
||||
* Spawn meshes on the position of the anchors of each room from a random stream.
|
||||
* The actors should have Z as up Y as right and X as forward.
|
||||
* The pivot point should be in the bottom center.
|
||||
* @param SpawnGroups A map which tells to spawn which actor to a given label.
|
||||
* @param RandomStream A random generator to choose randomly between actor classes if there a multiple for one label.
|
||||
* @param CutHoleLabels Labels for which the generated mesh should have holes. Only works with planes.
|
||||
* @param ProceduralMaterial Material to apply on top of the procedural mesh if any.
|
||||
* @param ShouldFallbackToProcedural Whether or not it should by default fallback to generating a procedural mesh if no actor class has been specified for a label.
|
||||
* @return The spawned actors.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DeprecatedFunction, DeprecationMessage = "Use AMRUKAnchorActorSpawner instead."), Category = "MR Utility Kit")
|
||||
TArray<AActor*> SpawnInteriorFromStream(const TMap<FString, FMRUKSpawnGroup>& SpawnGroups, const FRandomStream& RandomStream, const TArray<FString>& CutHoleLabels, UMaterialInterface* ProceduralMaterial = nullptr, bool ShouldFallbackToProcedural = true);
|
||||
|
||||
/**
|
||||
* Launch the scene capture. After a successful capture the scene should be updated.
|
||||
* @return Whether the capture was successful.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
||||
bool LaunchSceneCapture();
|
||||
|
||||
public:
|
||||
void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
void Deinitialize() override;
|
||||
|
||||
TSharedRef<FJsonObject> JsonSerialize();
|
||||
void UnregisterRoom(AMRUKRoom* Room);
|
||||
// Calculate the bounds of an Actor class and return it, the result is saved in a cache for faster lookup.
|
||||
FBox GetActorClassBounds(TSubclassOf<AActor> Actor);
|
||||
UOculusXRRoomLayoutManagerComponent* GetRoomLayoutManager();
|
||||
|
||||
private:
|
||||
AMRUKRoom* SpawnRoom();
|
||||
|
||||
void FinishedLoading(bool Success);
|
||||
|
||||
// FTickableGameObject interface
|
||||
virtual void Tick(float DeltaTime) override;
|
||||
virtual bool IsTickable() const override;
|
||||
virtual ETickableTickType GetTickableTickType() const override { return (HasAnyFlags(RF_ClassDefaultObject) ? ETickableTickType::Never : ETickableTickType::Conditional); }
|
||||
virtual TStatId GetStatId() const override { RETURN_QUICK_DECLARE_CYCLE_STAT(UMRUKSubsystem, STATGROUP_Tickables); }
|
||||
virtual UWorld* GetTickableGameObjectWorld() const override { return GetWorld(); }
|
||||
// ~FTickableGameObject interface
|
||||
|
||||
UFUNCTION()
|
||||
void SceneDataLoadedComplete(bool Success);
|
||||
UFUNCTION()
|
||||
void UpdatedSceneDataLoadedComplete(bool Success);
|
||||
UFUNCTION()
|
||||
void SceneCaptureComplete(FOculusXRUInt64 RequestId, bool bSuccess);
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UMRUKSceneData> SceneData = nullptr;
|
||||
|
||||
UPROPERTY()
|
||||
AActor* RoomLayoutManagerActor = nullptr;
|
||||
UPROPERTY()
|
||||
UOculusXRRoomLayoutManagerComponent* RoomLayoutManager = nullptr;
|
||||
UPROPERTY()
|
||||
mutable AMRUKRoom* CachedCurrentRoom = nullptr;
|
||||
mutable int64 CachedCurrentRoomFrame = 0;
|
||||
UPROPERTY()
|
||||
AActor* PositionGenerator = nullptr;
|
||||
|
||||
TMap<TSubclassOf<AActor>, FBox> ActorClassBoundsCache;
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OculusXRTelemetry.h"
|
||||
|
||||
namespace MRUKTelemetry
|
||||
{
|
||||
using FLoadGuardianMarker = OculusXRTelemetry::TMarker<257237531>;
|
||||
using FLoadBlobShadowMarker = OculusXRTelemetry::TMarker<257244458>;
|
||||
using FLoadLightDispatcherMarker = OculusXRTelemetry::TMarker<257234454>;
|
||||
using FLoadDebugComponentMarker = OculusXRTelemetry::TMarker<257232584>;
|
||||
using FLoadAnchorActorSpawnerMarker = OculusXRTelemetry::TMarker<257232670>;
|
||||
using FLoadSceneFromDeviceMarker = OculusXRTelemetry::TMarker<257235234>;
|
||||
using FLoadSceneFromJsonMarker = OculusXRTelemetry::TMarker<257237876>;
|
||||
using FLoadGridSliceResizerMarker = OculusXRTelemetry::TMarker<257238248>;
|
||||
using FLoadDestructibleGlobalMeshSpawner = OculusXRTelemetry::TMarker<257232038>;
|
||||
} // namespace MRUKTelemetry
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class MRUtilityKitEditor : ModuleRules
|
||||
{
|
||||
public MRUtilityKitEditor(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
bUseUnity = true;
|
||||
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
});
|
||||
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"OculusXRHMD",
|
||||
"OculusXRAnchors",
|
||||
"OculusXRScene",
|
||||
"Json",
|
||||
"UnrealEd",
|
||||
"RHI",
|
||||
"RenderCore",
|
||||
"ProceduralMeshComponent",
|
||||
"MRUtilityKit",
|
||||
});
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,214 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "DistanceMapTestData.h"
|
||||
#include "Editor.h"
|
||||
#include "Editor/UnrealEdEngine.h"
|
||||
#include "Engine/CanvasRenderTarget2D.h"
|
||||
#include "HAL/PlatformFileManager.h"
|
||||
#include "Misc/EngineVersionComparison.h"
|
||||
#include "MRUtilityKitDistanceMapGenerator.h"
|
||||
#include "MRUtilityKitSubsystem.h"
|
||||
#include "TestHelper.h"
|
||||
#include "Tests/AutomationEditorCommon.h"
|
||||
#include "TextureResource.h"
|
||||
#include "UnrealEdGlobals.h"
|
||||
|
||||
static void WriteTGA(const FString& FilePath, const TArray<FColor>& Pixels, int32 Width, int32 Height)
|
||||
{
|
||||
// Create file
|
||||
IFileHandle* FileHandle = FPlatformFileManager::Get().GetPlatformFile().OpenWrite(*FilePath);
|
||||
if (FileHandle)
|
||||
{
|
||||
// TGA File Header
|
||||
uint8 TGAHeader[18] = {};
|
||||
TGAHeader[2] = 2; // Uncompressed Type
|
||||
TGAHeader[12] = Width & 0xFF;
|
||||
TGAHeader[13] = (Width >> 8) & 0xFF;
|
||||
TGAHeader[14] = Height & 0xFF;
|
||||
TGAHeader[15] = (Height >> 8) & 0xFF;
|
||||
TGAHeader[16] = 32; // Bits per Pixel
|
||||
TGAHeader[17] = 0x20; // Top-Down, Non-Interlaced
|
||||
// Write TGA Header
|
||||
FileHandle->Write(TGAHeader, sizeof(TGAHeader));
|
||||
// Write Pixels
|
||||
for (int32 i = 0; i < Width * Height; i++)
|
||||
{
|
||||
const FColor& Pixel = Pixels[i];
|
||||
uint8 BGRA[4] = { Pixel.B, Pixel.G, Pixel.R, Pixel.A };
|
||||
FileHandle->Write(BGRA, sizeof(BGRA));
|
||||
}
|
||||
|
||||
// Close the file
|
||||
delete FileHandle;
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("Failed to open file for writing: %s"), *FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
static TArray<FColor> ReadTGAFromMemory(const uint8* Data, size_t DataSize)
|
||||
{
|
||||
TArray<FColor> Pixels;
|
||||
|
||||
// Read TGA Header
|
||||
uint8 TGAHeader[18];
|
||||
memcpy(TGAHeader, Data, sizeof(TGAHeader));
|
||||
// Get image dimensions from header
|
||||
const int32 Width = TGAHeader[12] | (TGAHeader[13] << 8);
|
||||
const int32 Height = TGAHeader[14] | (TGAHeader[15] << 8);
|
||||
|
||||
if (Width * Height > DataSize)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
// Resize pixel array
|
||||
Pixels.SetNumUninitialized(Width * Height);
|
||||
// Read Pixels
|
||||
uint32 Offset = sizeof(TGAHeader);
|
||||
for (int32 i = 0; i < Width * Height; i++)
|
||||
{
|
||||
uint8 BGRA[4];
|
||||
memcpy(BGRA, Data + Offset, sizeof(BGRA));
|
||||
Offset += sizeof(BGRA);
|
||||
// Convert from BGRA to RGBA
|
||||
Pixels[i] = FColor(BGRA[2], BGRA[1], BGRA[0], BGRA[3]);
|
||||
}
|
||||
|
||||
return Pixels;
|
||||
}
|
||||
|
||||
static TArray<FColor> ReadTGAFromFile(const FString& FilePath)
|
||||
{
|
||||
IFileHandle* FileHandle = FPlatformFileManager::Get().GetPlatformFile().OpenRead(*FilePath);
|
||||
TArray<uint8_t> Data;
|
||||
Data.SetNum(FileHandle->Size());
|
||||
FileHandle->Read(Data.GetData(), Data.Num());
|
||||
return ReadTGAFromMemory(Data.GetData(), Data.Num());
|
||||
}
|
||||
|
||||
#if UE_VERSION_OLDER_THAN(5, 5, 0)
|
||||
BEGIN_DEFINE_SPEC(FMRUKDistanceMapSpec, TEXT("MR Utility Kit"), EAutomationTestFlags::ProductFilter | EAutomationTestFlags::ApplicationContextMask)
|
||||
#else
|
||||
BEGIN_DEFINE_SPEC(FMRUKDistanceMapSpec, TEXT("MR Utility Kit"), EAutomationTestFlags::ProductFilter | EAutomationTestFlags_ApplicationContextMask)
|
||||
#endif
|
||||
END_DEFINE_SPEC(FMRUKDistanceMapSpec)
|
||||
|
||||
void FMRUKDistanceMapSpec::Define()
|
||||
{
|
||||
Describe(TEXT("Distance map"), [this] {
|
||||
BeforeEach([this]() {
|
||||
// Load map
|
||||
const auto ContentDir = FPaths::ProjectContentDir();
|
||||
FAutomationEditorCommonUtils::LoadMap(ContentDir + "/Common/Maps/TestLevel.umap");
|
||||
StartPIE(true);
|
||||
});
|
||||
|
||||
BeforeEach(EAsyncExecution::ThreadPool, []() {
|
||||
while (!GEditor->IsPlayingSessionInEditor())
|
||||
{
|
||||
// Wait until play session starts
|
||||
FGenericPlatformProcess::Yield();
|
||||
}
|
||||
});
|
||||
|
||||
It(TEXT("Capture distance map"), [this] {
|
||||
const auto World = GEditor->GetPIEWorldContext()->World();
|
||||
const auto GameInstance = World->GetGameInstance();
|
||||
UMRUKSubsystem* Subsystem = GameInstance->GetSubsystem<UMRUKSubsystem>();
|
||||
Subsystem->LoadSceneFromJsonString(ExampleRoomJson);
|
||||
|
||||
// Create and setup distance map generator
|
||||
const FActorSpawnParameters Params{};
|
||||
AMRUKDistanceMapGenerator* DistanceMapGenerator = World->SpawnActor<AMRUKDistanceMapGenerator>(Params);
|
||||
DistanceMapGenerator->SetActorLocation(FVector(0.0, 0.0, 200.0));
|
||||
DistanceMapGenerator->SetActorRotation(FRotator::MakeFromEuler(FVector(0.0, -90.0, 0.0)));
|
||||
|
||||
UCanvasRenderTarget2D* RenderTarget = Cast<UCanvasRenderTarget2D>(DistanceMapGenerator->CaptureDistanceMap());
|
||||
FTextureRenderTargetResource* RenderTargetResource = RenderTarget->GameThread_GetRenderTargetResource();
|
||||
// Create an array to store the pixel data
|
||||
TArray<FColor> PixelData;
|
||||
// Read the pixel data from the Render Target
|
||||
ENQUEUE_RENDER_COMMAND(ReadSurfaceCommand)
|
||||
(
|
||||
[RenderTargetResource, &PixelData](FRHICommandListImmediate& RHICmdList) {
|
||||
const FTextureRHIRef Texture2DRHI = RenderTargetResource->GetRenderTargetTexture();
|
||||
RHICmdList.ReadSurfaceData(
|
||||
Texture2DRHI,
|
||||
FIntRect(0, 0, Texture2DRHI->GetSizeX(), Texture2DRHI->GetSizeY()),
|
||||
PixelData,
|
||||
FReadSurfaceDataFlags());
|
||||
});
|
||||
// Wait for the rendering thread to finish executing the command
|
||||
FlushRenderingCommands();
|
||||
|
||||
// Compare result
|
||||
|
||||
const TArray<FColor> ExpectedPixels = ReadTGAFromMemory(DistanceMapTestData, sizeof(DistanceMapTestData));
|
||||
|
||||
bool Success = true;
|
||||
if (!TestEqual(TEXT("Pixel count matches"), PixelData.Num(), ExpectedPixels.Num()))
|
||||
{
|
||||
Success = false;
|
||||
}
|
||||
for (int32 I = 0; I < PixelData.Num() && Success; ++I)
|
||||
{
|
||||
if (!TestEqual(TEXT("R channel matches"), PixelData[I].R, ExpectedPixels[I].R))
|
||||
{
|
||||
Success = false;
|
||||
break;
|
||||
}
|
||||
if (!TestEqual(TEXT("G channel matches"), PixelData[I].G, ExpectedPixels[I].G))
|
||||
{
|
||||
Success = false;
|
||||
break;
|
||||
}
|
||||
if (!TestEqual(TEXT("B channel matches"), PixelData[I].B, ExpectedPixels[I].B))
|
||||
{
|
||||
Success = false;
|
||||
break;
|
||||
}
|
||||
if (!TestEqual(TEXT("A channel matches"), PixelData[I].A, ExpectedPixels[I].A))
|
||||
{
|
||||
Success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Success)
|
||||
{
|
||||
const FString& TestResultsPath = FPaths::Combine(FPaths::ProjectIntermediateDir(), "TestResults");
|
||||
|
||||
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
|
||||
if (!PlatformFile.DirectoryExists(*TestResultsPath))
|
||||
{
|
||||
PlatformFile.CreateDirectory(*TestResultsPath);
|
||||
}
|
||||
|
||||
const FString& ActualPixelsFilename = FPaths::CreateTempFilename(*TestResultsPath, TEXT("DistanceMapTest_ActualPixels"), TEXT(".tga"));
|
||||
const FString& ExpectedPixelsFilename = FPaths::CreateTempFilename(*TestResultsPath, TEXT("DistanceMapTest_ExpectedPixels"), TEXT(".tga"));
|
||||
// The resulting binary data can be converted to C array with the tool found here https://github.com/AntumDeluge/bin2header
|
||||
WriteTGA(ActualPixelsFilename, PixelData, RenderTarget->SizeX, RenderTarget->SizeY);
|
||||
WriteTGA(ExpectedPixelsFilename, PixelData, RenderTarget->SizeX, RenderTarget->SizeY);
|
||||
UE_LOG(LogTemp, Warning, TEXT("Expected pixels have been saved as tga file in %s"), *ExpectedPixelsFilename);
|
||||
UE_LOG(LogTemp, Warning, TEXT("Actual pixels have been saved as tga file in %s"), *ActualPixelsFilename);
|
||||
}
|
||||
});
|
||||
|
||||
// Caution: Order of these statements is important
|
||||
|
||||
AfterEach(EAsyncExecution::ThreadPool, []() {
|
||||
while (GEditor->IsPlayingSessionInEditor())
|
||||
{
|
||||
// Wait until play session ends
|
||||
FGenericPlatformProcess::Yield();
|
||||
}
|
||||
});
|
||||
|
||||
AfterEach([]() {
|
||||
// Request end of play session
|
||||
GUnrealEd->RequestEndPlayMap();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitEditor.h"
|
||||
|
||||
#include "MRUtilityKitGridSliceResizer.h"
|
||||
#include "MRUtilityKitGridSliceResizerVisualization.h"
|
||||
#include "MRUtilityKitTelemetry.h"
|
||||
#include "UnrealEdGlobals.h"
|
||||
#include "Editor/UnrealEdEngine.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FMRUKEditorModule"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogMRUKEditor);
|
||||
|
||||
void FMRUKEditorModule::StartupModule()
|
||||
{
|
||||
if (GUnrealEd)
|
||||
{
|
||||
const auto ResizerVisualizer = MakeShared<FMRUKGridSliceResizerVisualizer>();
|
||||
GUnrealEd->RegisterComponentVisualizer(UMRUKGridSliceResizerComponent::StaticClass()->GetFName(), ResizerVisualizer);
|
||||
ResizerVisualizer->OnRegister();
|
||||
}
|
||||
}
|
||||
|
||||
void FMRUKEditorModule::ShutdownModule()
|
||||
{
|
||||
if (GUnrealEd)
|
||||
{
|
||||
GUnrealEd->UnregisterComponentVisualizer(UMRUKGridSliceResizerComponent::StaticClass()->GetFName());
|
||||
}
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FMRUKEditorModule, MRUtilityKitEditor)
|
||||
@@ -0,0 +1,165 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "Editor/UnrealEdEngine.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "Misc/EngineVersionComparison.h"
|
||||
#include "MRUtilityKitGeometry.h"
|
||||
#include "TestHelper.h"
|
||||
#include "Tests/AutomationEditorCommon.h"
|
||||
#include "UnrealEdGlobals.h"
|
||||
#include "Editor.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
double CalculateTriangleArea(const FVector2D& P1, const FVector2D& P2, const FVector2D& P3)
|
||||
{
|
||||
return (P1.X * (P2.Y - P3.Y) + P2.X * (P3.Y - P1.Y) + P3.X * (P1.Y - P2.Y)) / 2.0f;
|
||||
}
|
||||
|
||||
// Use the triangulated area as a proxy to ensure the triangulation worked as expected
|
||||
double CalculateTriangulatedArea(const TArray<FVector2D>& Vertices, const TArray<int32>& Indices)
|
||||
{
|
||||
double Area = 0.0;
|
||||
for (int i = 0; i < Indices.Num(); i += 3)
|
||||
{
|
||||
const FVector2D& P1 = Vertices[Indices[i]];
|
||||
const FVector2D& P2 = Vertices[Indices[i + 1]];
|
||||
const FVector2D& P3 = Vertices[Indices[i + 2]];
|
||||
const double TriangleArea = CalculateTriangleArea(P1, P2, P3);
|
||||
check(TriangleArea >= 0.0);
|
||||
Area += TriangleArea;
|
||||
}
|
||||
|
||||
return Area;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#if UE_VERSION_OLDER_THAN(5, 5, 0)
|
||||
BEGIN_DEFINE_SPEC(FMRUKGeometrySpec, TEXT("MR Utility Kit"), EAutomationTestFlags::ProductFilter | EAutomationTestFlags::ApplicationContextMask)
|
||||
#else
|
||||
BEGIN_DEFINE_SPEC(FMRUKGeometrySpec, TEXT("MR Utility Kit"), EAutomationTestFlags::ProductFilter | EAutomationTestFlags_ApplicationContextMask)
|
||||
#endif
|
||||
|
||||
void SetupMRUKSubsystem();
|
||||
void TeardownMRUKSubsystem();
|
||||
END_DEFINE_SPEC(FMRUKGeometrySpec)
|
||||
|
||||
void FMRUKGeometrySpec::SetupMRUKSubsystem()
|
||||
{
|
||||
BeforeEach([this]() {
|
||||
// Load map and start play in editor
|
||||
const auto ContentDir = FPaths::ProjectContentDir();
|
||||
FAutomationEditorCommonUtils::LoadMap(ContentDir + "/Common/Maps/TestLevel.umap");
|
||||
StartPIE(true);
|
||||
});
|
||||
|
||||
BeforeEach(EAsyncExecution::ThreadPool, []() {
|
||||
while (!GEditor->IsPlayingSessionInEditor())
|
||||
{
|
||||
// Wait until play session starts
|
||||
FGenericPlatformProcess::Yield();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void FMRUKGeometrySpec::TeardownMRUKSubsystem()
|
||||
{
|
||||
// Caution: Order of these statements is important
|
||||
|
||||
AfterEach(EAsyncExecution::ThreadPool, []() {
|
||||
while (GEditor->IsPlayingSessionInEditor())
|
||||
{
|
||||
// Wait until play session ends
|
||||
FGenericPlatformProcess::Yield();
|
||||
}
|
||||
});
|
||||
|
||||
AfterEach([]() {
|
||||
// Request end of play session
|
||||
GUnrealEd->RequestEndPlayMap();
|
||||
});
|
||||
}
|
||||
|
||||
void FMRUKGeometrySpec::Define()
|
||||
{
|
||||
Describe(TEXT("Triangulation"), [this] {
|
||||
SetupMRUKSubsystem();
|
||||
|
||||
It(TEXT("Triangulate quad"), [this] {
|
||||
const TArray<FVector2f> TestPolygon = { { 0.0f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 1.0f } };
|
||||
TArray<FVector2D> Vertices;
|
||||
TArray<int32> Indices;
|
||||
MRUKTriangulatePolygon({ TestPolygon }, Vertices, Indices);
|
||||
TestEqual(TEXT("Correct number of indices"), 6, Indices.Num());
|
||||
TestEqual(TEXT("Correct area triangulated"), 1.0, CalculateTriangulatedArea(Vertices, Indices));
|
||||
});
|
||||
|
||||
It(TEXT("Triangulate quad with hole"), [this] {
|
||||
const TArray<TArray<FVector2f>> Polygons = { { { 0.0f, 0.0f }, { 2.0f, 0.0f }, { 2.0f, 2.0f }, { 0.0f, 2.0f } }, { { 0.5f, 0.5f }, { 0.5f, 1.5f }, { 1.5f, 1.5f }, { 1.5f, 0.5f } } };
|
||||
TArray<FVector2D> Vertices;
|
||||
TArray<int32> Indices;
|
||||
MRUKTriangulatePolygon(Polygons, Vertices, Indices);
|
||||
|
||||
TestEqual(TEXT("Correct number of indices"), Indices.Num(), 24);
|
||||
TestEqual(TEXT("Correct area triangulated"), CalculateTriangulatedArea(Vertices, Indices), 3.0);
|
||||
});
|
||||
|
||||
It(TEXT("Triangulate quad with four holes"), [this] {
|
||||
TArray<TArray<FVector2f>> Polygons = { { { 0.0f, 0.0f }, { 4.0f, 0.0f }, { 4.0f, 4.0f }, { 0.0f, 4.0f } } };
|
||||
for (int32 I = 0; I < 4; ++I)
|
||||
{
|
||||
const FVector2f Offset(0.5 + 2.0 * (I / 2), 0.5 + 2.0 * (I % 2));
|
||||
Polygons.Push({ Offset + FVector2f(0.0, 0.0), Offset + FVector2f(0.0, 1.0), Offset + FVector2f(1.0, 1.0), Offset + FVector2f(1.0, 0.0) });
|
||||
}
|
||||
|
||||
TArray<FVector2D> Vertices;
|
||||
TArray<int32> Indices;
|
||||
MRUKTriangulatePolygon(Polygons, Vertices, Indices);
|
||||
|
||||
TestEqual(TEXT("Correct number of indices"), Indices.Num(), 66);
|
||||
TestEqual(TEXT("Correct area triangulated"), CalculateTriangulatedArea(Vertices, Indices), 12.0);
|
||||
});
|
||||
|
||||
It(TEXT("Triangulate quad with two close holes"), [this] {
|
||||
const TArray<TArray<FVector2f>> Polygons = {
|
||||
{
|
||||
{ 101.985214, 113.8258 },
|
||||
{ -101.985214, 113.8258 },
|
||||
{ -101.985214, -113.8258 },
|
||||
{ 101.985214, -113.8258 },
|
||||
},
|
||||
{ { 18.395055731633885, 9.0596833 }, { -72.518264268366110, 9.0596833 }, { -72.518264268366110, 67.2252527 }, { 18.395055731633885, 67.2252527 } },
|
||||
{ { 18.395055731633885, -53.4203167 }, { -72.518264268366110, -53.4203167 }, { -72.518264268366110, 4.7452569 }, { 18.395055731633885, 4.7452569 } },
|
||||
};
|
||||
|
||||
TArray<FVector2D> Vertices;
|
||||
TArray<int32> Indices;
|
||||
MRUKTriangulatePolygon(Polygons, Vertices, Indices);
|
||||
|
||||
TestEqual(TEXT("Correct number of indices"), Indices.Num(), 42);
|
||||
TestEqual(TEXT("Correct area triangulated"), CalculateTriangulatedArea(Vertices, Indices), 35858.143857, 0.001);
|
||||
});
|
||||
|
||||
It(TEXT("Triangulate LShape"), [this] {
|
||||
const TArray<FVector2f> TestPolygon = { { 0.0, 0.0 }, { 2.0, 0.0 }, { 2.0, 2.0 }, { 1.0, 2.0 }, { 1.0, 1.0 }, { 0.0, 1.0 } };
|
||||
TArray<FVector2D> Vertices;
|
||||
TArray<int32> Indices;
|
||||
MRUKTriangulatePolygon({ TestPolygon }, Vertices, Indices);
|
||||
|
||||
TestEqual(TEXT("Correct number of indices"), Indices.Num(), 12);
|
||||
TestEqual(TEXT("Correct area triangulated"), CalculateTriangulatedArea(Vertices, Indices), 3.0);
|
||||
});
|
||||
|
||||
It(TEXT("Triangulate CShape"), [this] {
|
||||
const TArray<FVector2f> TestPolygon = { { 0.0, 0.0 }, { 2.0, 0.0 }, { 2.0, 1.0 }, { 1.0, 1.0 }, { 1.0, 2.0 }, { 2.0, 2.0 }, { 2.0, 3.0 }, { 0.0, 3.0 } };
|
||||
TArray<FVector2D> Vertices;
|
||||
TArray<int32> Indices;
|
||||
MRUKTriangulatePolygon({ TestPolygon }, Vertices, Indices);
|
||||
|
||||
TestEqual(TEXT("Correct number of indices"), Indices.Num(), 18);
|
||||
TestEqual(TEXT("Correct area triangulated"), CalculateTriangulatedArea(Vertices, Indices), 5.0);
|
||||
});
|
||||
|
||||
TeardownMRUKSubsystem();
|
||||
});
|
||||
}
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "Editor/UnrealEdEngine.h"
|
||||
#include "Editor.h"
|
||||
#include "GridSliceResizerTestData.h"
|
||||
#include "MRUtilityKitGridSliceResizer.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "Misc/EngineVersionComparison.h"
|
||||
#include "TestHelper.h"
|
||||
#include "Tests/AutomationEditorCommon.h"
|
||||
#include "UnrealEdGlobals.h"
|
||||
|
||||
#if UE_VERSION_OLDER_THAN(5, 5, 0)
|
||||
BEGIN_DEFINE_SPEC(FMRUKGridSliceResizerSpec, TEXT("MR Utility Kit"), EAutomationTestFlags::ProductFilter | EAutomationTestFlags::ApplicationContextMask)
|
||||
#else
|
||||
BEGIN_DEFINE_SPEC(FMRUKGridSliceResizerSpec, TEXT("MR Utility Kit"), EAutomationTestFlags::ProductFilter | EAutomationTestFlags_ApplicationContextMask)
|
||||
#endif
|
||||
END_DEFINE_SPEC(FMRUKGridSliceResizerSpec)
|
||||
|
||||
void FMRUKGridSliceResizerSpec::Define()
|
||||
{
|
||||
Describe(TEXT("Grid Slice Resizer"), [this] {
|
||||
BeforeEach([this]() {
|
||||
// Load map
|
||||
const auto ContentDir = FPaths::ProjectContentDir();
|
||||
FAutomationEditorCommonUtils::LoadMap(ContentDir + "/Common/Maps/TestLevel.umap");
|
||||
StartPIE(true);
|
||||
});
|
||||
|
||||
BeforeEach(EAsyncExecution::ThreadPool, []() {
|
||||
while (!GEditor->IsPlayingSessionInEditor())
|
||||
{
|
||||
// Wait until play session starts
|
||||
FGenericPlatformProcess::Yield();
|
||||
}
|
||||
});
|
||||
|
||||
It(TEXT("Slice mesh"), [this] {
|
||||
const auto World = GEditor->GetPIEWorldContext()->World();
|
||||
const FActorSpawnParameters Params{};
|
||||
AMeshResizer* Resizer = World->SpawnActor<AMeshResizer>(Params);
|
||||
|
||||
UMRUKGridSliceResizerComponent* ResizerComponent = Resizer->GridSliceResizerComponent;
|
||||
|
||||
struct FTestData
|
||||
{
|
||||
FVector Scale;
|
||||
uint8 ScaleCenterMode;
|
||||
double BorderXNegative;
|
||||
double BorderXPositive;
|
||||
double BorderYNegative;
|
||||
double BorderYPositive;
|
||||
double BorderZNegative;
|
||||
double BorderZPositive;
|
||||
FVector SlicerPivotOffset;
|
||||
const TArray<FVector>& ExpectedPositions;
|
||||
};
|
||||
|
||||
TArray TestDataContainer = {
|
||||
// Test without pivot offset
|
||||
FTestData{
|
||||
FVector(2.0, 2.0, 2.0),
|
||||
(uint8)EMRUKScaleCenterMode::XAxis | (uint8)EMRUKScaleCenterMode::YAxis | (uint8)EMRUKScaleCenterMode::ZAxis,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
FVector(0.0, 0.0, 0.0),
|
||||
ExpectedPositionsPivotCenter,
|
||||
},
|
||||
// Test with pivot offset
|
||||
FTestData{
|
||||
FVector(2.0, 2.0, 2.0),
|
||||
(uint8)EMRUKScaleCenterMode::XAxis | (uint8)EMRUKScaleCenterMode::YAxis | (uint8)EMRUKScaleCenterMode::ZAxis,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
FVector(0.0, 0.0, -40.0),
|
||||
ExpectedPositionsPivotOffset,
|
||||
},
|
||||
// Test with pivot outside of bounding box
|
||||
FTestData{
|
||||
FVector(2.0, 2.0, 2.0),
|
||||
(uint8)EMRUKScaleCenterMode::XAxis | (uint8)EMRUKScaleCenterMode::YAxis | (uint8)EMRUKScaleCenterMode::ZAxis,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
FVector(0.0, 0.0, -160.0),
|
||||
ExpectedPositionsPivotOutside,
|
||||
},
|
||||
// Test scaled down with pivot offset
|
||||
FTestData{
|
||||
FVector(0.2, 0.2, 0.2),
|
||||
(uint8)EMRUKScaleCenterMode::XAxis | (uint8)EMRUKScaleCenterMode::YAxis | (uint8)EMRUKScaleCenterMode::ZAxis,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
FVector(0.0, 0.0, 20.0),
|
||||
ExpectedPositionsScaledDown,
|
||||
},
|
||||
// Test without center scale
|
||||
FTestData{
|
||||
FVector(2.0, 2.0, 2.0),
|
||||
(uint8)EMRUKScaleCenterMode::None,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
FVector(0.0, 0.0, 0.0),
|
||||
ExpectedPositionsCenterNotScaled,
|
||||
},
|
||||
// Test without center scale but scaled down
|
||||
FTestData{
|
||||
FVector(0.2, 0.2, 0.2),
|
||||
(uint8)EMRUKScaleCenterMode::None,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
FVector(0.0, 0.0, 0.0),
|
||||
ExpectedPositionsScaledDownCenterNotScaled,
|
||||
}
|
||||
};
|
||||
|
||||
constexpr double Tolerance = 0.001;
|
||||
|
||||
for (const FTestData& TestData : TestDataContainer)
|
||||
{
|
||||
ResizerComponent->ScaleCenterMode = TestData.ScaleCenterMode;
|
||||
ResizerComponent->BorderXNegative = TestData.BorderXNegative;
|
||||
ResizerComponent->BorderXPositive = TestData.BorderXPositive;
|
||||
ResizerComponent->BorderYNegative = TestData.BorderYNegative;
|
||||
ResizerComponent->BorderYPositive = TestData.BorderYPositive;
|
||||
ResizerComponent->BorderZNegative = TestData.BorderZNegative;
|
||||
ResizerComponent->BorderZPositive = TestData.BorderZPositive;
|
||||
ResizerComponent->SlicerPivotOffset = TestData.SlicerPivotOffset;
|
||||
|
||||
Resizer->SetActorScale3D(TestData.Scale);
|
||||
Resizer->GridSliceResizerComponent->SliceMesh();
|
||||
|
||||
const FProcMeshSection* MeshSection = Resizer->GridSliceResizerComponent->ProcMesh->GetProcMeshSection(0);
|
||||
if (!TestNotNull(TEXT("Mesh section is not null"), MeshSection))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const TArray<uint32>& Indices = MeshSection->ProcIndexBuffer;
|
||||
const TArray<FProcMeshVertex>& Vertices = MeshSection->ProcVertexBuffer;
|
||||
|
||||
TestEqual(TEXT("Triangles count matches"), Indices.Num(), ExpectedTriangles.Num());
|
||||
for (int32 I = 0; I < Indices.Num(); ++I)
|
||||
{
|
||||
TestEqual(TEXT("Index matches"), Indices[I], ExpectedTriangles[I]);
|
||||
}
|
||||
|
||||
TestEqual(TEXT("Positions count matches"), Vertices.Num(), TestData.ExpectedPositions.Num());
|
||||
for (int32 I = 0; I < Vertices.Num(); ++I)
|
||||
{
|
||||
TestEqual(TEXT("Position matches"), Vertices[I].Position, TestData.ExpectedPositions[I], Tolerance);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Caution: Order of these statements is important
|
||||
|
||||
AfterEach(EAsyncExecution::ThreadPool, []() {
|
||||
while (GEditor->IsPlayingSessionInEditor())
|
||||
{
|
||||
// Wait until play session ends
|
||||
FGenericPlatformProcess::Yield();
|
||||
}
|
||||
});
|
||||
|
||||
AfterEach([]() {
|
||||
// Request end of play session
|
||||
GUnrealEd->RequestEndPlayMap();
|
||||
});
|
||||
});
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitGridSliceResizerVisualization.h"
|
||||
|
||||
#include "MRUtilityKitGridSliceResizer.h"
|
||||
#include "ProceduralMeshComponent.h"
|
||||
#include "SceneManagement.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
void DrawSliceBorder(FPrimitiveDrawInterface* PDI, FVector Scale, FVector Pivot, FVector ScaledPivot, const UProceduralMeshComponent* ProcMesh, const FVector& Size, double BorderPos, double BorderNeg, int32 Axis)
|
||||
{
|
||||
// Scaled bounding box of the mesh that gets displayed
|
||||
FBox BBox = ProcMesh->GetLocalBounds().GetBox();
|
||||
BBox.Min *= Scale;
|
||||
BBox.Max *= Scale;
|
||||
|
||||
const FVector InvSize = FVector(1.0, 1.0, 1.0) / Scale;
|
||||
// Unscaled bounding box
|
||||
const FBox BBoxOriginal = FBox(BBox.Min * InvSize, BBox.Max * InvSize);
|
||||
|
||||
double Positive = BBox.Max[Axis] - (BBoxOriginal.Max[Axis] - (FMath::Abs(BBoxOriginal.Max[Axis] - Pivot[Axis]) * BorderPos + Pivot[Axis]));
|
||||
if (Positive + Pivot[Axis] < 0.0)
|
||||
{
|
||||
// Clamp to the bounding box in case the bounding box is smaller than the stubs
|
||||
Positive = BBox.Max[Axis];
|
||||
}
|
||||
if (Pivot[Axis] > BBox.Max[Axis])
|
||||
{
|
||||
// Clamp to the pivot if the pivot is outside of the bounding box
|
||||
Positive = FMath::Min(ScaledPivot[Axis], Positive);
|
||||
}
|
||||
|
||||
double Negative = BBox.Min[Axis] - (BBoxOriginal.Min[Axis] - (-FMath::Abs(BBoxOriginal.Min[Axis] - Pivot[Axis]) * BorderNeg + Pivot[Axis]));
|
||||
if (Negative - Pivot[Axis] > 0.0)
|
||||
{
|
||||
// Clamp to the bounding box in case the bounding box is smaller than the stubs
|
||||
Negative = BBox.Min[Axis];
|
||||
}
|
||||
if (Pivot[Axis] < BBox.Min[Axis])
|
||||
{
|
||||
// Clamp to the pivot if the pivot is outside of the bounding box
|
||||
Negative = FMath::Max(ScaledPivot[Axis], Negative);
|
||||
}
|
||||
|
||||
double PosNeg[2] = { Positive, Negative };
|
||||
for (int32 J = 0; J < 2; ++J)
|
||||
{
|
||||
FVector Max;
|
||||
FVector Min;
|
||||
for (int32 I = 0; I < 3; ++I)
|
||||
{
|
||||
if (I == Axis)
|
||||
{
|
||||
Min[I] = PosNeg[J];
|
||||
Max[I] = PosNeg[J];
|
||||
}
|
||||
else
|
||||
{
|
||||
Min[I] = BBox.Min[I];
|
||||
Max[I] = BBox.Max[I];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Fix visualization for in editor playing. Meaning applying the world transform.
|
||||
const FBox Box(Min, Max);
|
||||
DrawWireBox(PDI, Box, FLinearColor(1.0f, 0.0f, 0.0f), SDPG_Foreground, 0.2f);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void FMRUKGridSliceResizerVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI)
|
||||
{
|
||||
const UMRUKGridSliceResizerComponent* Resizer = Cast<const UMRUKGridSliceResizerComponent>(Component);
|
||||
if (!Resizer || !Resizer->GetOwner())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FVector Size = Resizer->GetOwner()->GetActorScale();
|
||||
const FVector Location = Resizer->GetComponentLocation();
|
||||
const FVector Pivot = Location + Resizer->SlicerPivotOffset;
|
||||
const FVector ScaledPivot = Location + Resizer->SlicerPivotOffset * Size;
|
||||
|
||||
if (Resizer->bDebugDrawPivot)
|
||||
{
|
||||
// Draw pivot
|
||||
DrawWireSphere(PDI, ScaledPivot, FLinearColor(0.0f, 1.0f, 1.0f), 2.0, 16, SDPG_Foreground, 0.5f);
|
||||
}
|
||||
|
||||
if (const auto ProcMesh = Resizer->GetOwner()->GetComponentByClass<UProceduralMeshComponent>())
|
||||
{
|
||||
if (Resizer->bDebugDrawBorderX)
|
||||
{
|
||||
DrawSliceBorder(PDI, Size, Pivot, ScaledPivot, ProcMesh, Size, Resizer->BorderXPositive, Resizer->BorderXNegative, 0);
|
||||
}
|
||||
if (Resizer->bDebugDrawBorderY)
|
||||
{
|
||||
DrawSliceBorder(PDI, Size, Pivot, ScaledPivot, ProcMesh, Size, Resizer->BorderYPositive, Resizer->BorderYNegative, 1);
|
||||
}
|
||||
if (Resizer->bDebugDrawBorderZ)
|
||||
{
|
||||
DrawSliceBorder(PDI, Size, Pivot, ScaledPivot, ProcMesh, Size, Resizer->BorderZPositive, Resizer->BorderZNegative, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MeshActor.h"
|
||||
#include "UObject/ConstructorHelpers.h"
|
||||
#include "Components/StaticMeshComponent.h"
|
||||
#include "Engine/StaticMesh.h"
|
||||
|
||||
AMeshActor::AMeshActor()
|
||||
{
|
||||
static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeFinder(TEXT("StaticMesh'/Engine/BasicShapes/Cube.Cube'"));
|
||||
Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
|
||||
if (CubeFinder.Succeeded())
|
||||
{
|
||||
Mesh->SetStaticMesh(CubeFinder.Object);
|
||||
}
|
||||
SetRootComponent(Mesh);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "MeshActor.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class AMeshActor : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UStaticMeshComponent* Mesh;
|
||||
|
||||
public:
|
||||
AMeshActor();
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "MRUtilityKitAnchor.h"
|
||||
#include "MRUtilityKitGridSliceResizer.h"
|
||||
#include "TestHelper.generated.h"
|
||||
|
||||
bool StartPIE(bool bSimulateInEditor);
|
||||
|
||||
UCLASS()
|
||||
class URoomAndAnchorObserver : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY()
|
||||
TArray<AMRUKAnchor*> AnchorsCreated;
|
||||
UPROPERTY()
|
||||
TArray<AMRUKAnchor*> AnchorsUpdated;
|
||||
UPROPERTY()
|
||||
TArray<AMRUKAnchor*> AnchorsRemoved;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<AMRUKRoom*> RoomsCreated;
|
||||
UPROPERTY()
|
||||
TArray<AMRUKRoom*> RoomsUpdated;
|
||||
UPROPERTY()
|
||||
TArray<AMRUKRoom*> RoomsRemoved;
|
||||
|
||||
UFUNCTION()
|
||||
void OnAnchorCreated(AMRUKAnchor* Anchor);
|
||||
UFUNCTION()
|
||||
void OnAnchorUpdated(AMRUKAnchor* Anchor);
|
||||
UFUNCTION()
|
||||
void OnAnchorRemoved(AMRUKAnchor* Anchor);
|
||||
UFUNCTION()
|
||||
void OnRoomCreated(AMRUKRoom* Room);
|
||||
UFUNCTION()
|
||||
void OnRoomUpdated(AMRUKRoom* Room);
|
||||
UFUNCTION()
|
||||
void OnRoomRemoved(AMRUKRoom* Room);
|
||||
|
||||
void Clear();
|
||||
};
|
||||
|
||||
extern const TCHAR* ExampleRoomJson;
|
||||
extern const TCHAR* ExampleRoomFurnitureAddedJson;
|
||||
extern const TCHAR* ExampleRoomMoreFurnitureAddedJson;
|
||||
extern const TCHAR* ExampleRoomFurnitureModifiedJson;
|
||||
extern const TCHAR* ExampleOtherRoomJson;
|
||||
|
||||
UCLASS()
|
||||
class AMeshResizer : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY()
|
||||
UMRUKGridSliceResizerComponent* GridSliceResizerComponent;
|
||||
|
||||
AMeshResizer();
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogMRUKEditor, Log, All);
|
||||
|
||||
class FMRUKEditorModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
};
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ComponentVisualizer.h"
|
||||
|
||||
class MRUTILITYKITEDITOR_API FMRUKGridSliceResizerVisualizer : public FComponentVisualizer
|
||||
{
|
||||
private:
|
||||
virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override;
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
// @lint-ignore-every LICENSELINT
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
namespace UnrealBuildTool.Rules
|
||||
{
|
||||
public class OculusXRAnchors : ModuleRules
|
||||
{
|
||||
public OculusXRAnchors(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
bUseUnity = false;
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"HeadMountedDisplay",
|
||||
"OculusXRHMD",
|
||||
"OVRPluginXR",
|
||||
"XRBase",
|
||||
"OpenXR",
|
||||
"OpenXRHMD",
|
||||
"ProceduralMeshComponent",
|
||||
});
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"OculusXRAsyncRequest",
|
||||
"KhronosOpenXRHeaders",
|
||||
});
|
||||
|
||||
PrivateIncludePaths.AddRange(
|
||||
new string[] {
|
||||
// Relative to Engine\Plugins\Runtime\Oculus\OculusVR\Source
|
||||
"OculusXRHMD/Private",
|
||||
});
|
||||
|
||||
PublicIncludePaths.AddRange(
|
||||
new string[] {
|
||||
"Runtime/Engine/Classes/Components",
|
||||
});
|
||||
|
||||
AddEngineThirdPartyPrivateStaticDependencies(Target, "OpenXR");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRAnchorBPFunctionLibrary.h"
|
||||
|
||||
#include "Engine/GameEngine.h"
|
||||
#include "OculusXRAnchorsModule.h"
|
||||
#include "OculusXRHMD.h"
|
||||
#include "OculusXRSpatialAnchorComponent.h"
|
||||
#include "OculusXRAnchorsPrivate.h"
|
||||
#include "OculusXRAnchorManager.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
|
||||
AActor* UOculusXRAnchorBPFunctionLibrary::SpawnActorWithAnchorHandle(UObject* WorldContextObject, FOculusXRUInt64 Handle, FOculusXRUUID UUID, EOculusXRSpaceStorageLocation Location, UClass* ActorClass,
|
||||
AActor* Owner, APawn* Instigator, ESpawnActorCollisionHandlingMethod CollisionHandlingMethod)
|
||||
{
|
||||
FActorSpawnParameters SpawnInfo;
|
||||
SpawnInfo.Owner = Owner;
|
||||
SpawnInfo.Instigator = Instigator;
|
||||
SpawnInfo.ObjectFlags |= RF_Transient;
|
||||
SpawnInfo.SpawnCollisionHandlingOverride = CollisionHandlingMethod;
|
||||
|
||||
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
|
||||
if (World == nullptr)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid WorldContext Object for SpawnActorWithAnchorHandle."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AActor* NewSpatialAnchorActor = World->SpawnActor(ActorClass, nullptr, nullptr, SpawnInfo);
|
||||
if (NewSpatialAnchorActor == nullptr)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to spawn Actor in SpawnActorWithAnchorHandle"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UOculusXRSpatialAnchorComponent* SpatialAnchorComponent = NewSpatialAnchorActor->FindComponentByClass<UOculusXRSpatialAnchorComponent>();
|
||||
if (SpatialAnchorComponent == nullptr)
|
||||
{
|
||||
SpatialAnchorComponent = Cast<UOculusXRSpatialAnchorComponent>(NewSpatialAnchorActor->AddComponentByClass(UOculusXRSpatialAnchorComponent::StaticClass(), false, FTransform::Identity, false));
|
||||
}
|
||||
|
||||
if (!IsValid(SpatialAnchorComponent))
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to find or spawn Spatial Anchor component in SpawnActorWithAnchorHandle"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SpatialAnchorComponent->SetHandle(Handle);
|
||||
SpatialAnchorComponent->SetUUID(UUID);
|
||||
SpatialAnchorComponent->SetStoredLocation(Location, true);
|
||||
return NewSpatialAnchorActor;
|
||||
}
|
||||
|
||||
AActor* UOculusXRAnchorBPFunctionLibrary::SpawnActorWithAnchorQueryResults(UObject* WorldContextObject, const FOculusXRSpaceQueryResult& QueryResult, UClass* ActorClass, AActor* Owner, APawn* Instigator, ESpawnActorCollisionHandlingMethod CollisionHandlingMethod)
|
||||
{
|
||||
return SpawnActorWithAnchorHandle(WorldContextObject, QueryResult.Space, QueryResult.UUID, QueryResult.Location, ActorClass, Owner, Instigator, CollisionHandlingMethod);
|
||||
}
|
||||
|
||||
bool UOculusXRAnchorBPFunctionLibrary::GetAnchorComponentStatus(AActor* TargetActor, EOculusXRSpaceComponentType ComponentType, bool& bIsEnabled)
|
||||
{
|
||||
UOculusXRAnchorComponent* AnchorComponent = Cast<UOculusXRAnchorComponent>(TargetActor->GetComponentByClass(UOculusXRAnchorComponent::StaticClass()));
|
||||
|
||||
if (!IsValid(AnchorComponent))
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid Anchor Component provided to GetAnchorComponentStatus"));
|
||||
bIsEnabled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bOutIsEnabled = false;
|
||||
bool bIsChangePending = false;
|
||||
|
||||
EOculusXRAnchorResult::Type AnchorResult;
|
||||
bool bDidCallStart = OculusXRAnchors::FOculusXRAnchors::GetAnchorComponentStatus(AnchorComponent, ComponentType, bOutIsEnabled, bIsChangePending, AnchorResult);
|
||||
if (!bDidCallStart)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start call to internal GetAnchorComponentStatus"));
|
||||
bIsEnabled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bIsEnabled = bOutIsEnabled;
|
||||
return bIsEnabled;
|
||||
}
|
||||
|
||||
bool UOculusXRAnchorBPFunctionLibrary::GetAnchorTransformByHandle(const FOculusXRUInt64& Handle, FTransform& OutTransform)
|
||||
{
|
||||
FOculusXRAnchorLocationFlags AnchorFlags(0);
|
||||
return TryGetAnchorTransformByHandle(Handle, OutTransform, AnchorFlags);
|
||||
}
|
||||
|
||||
bool UOculusXRAnchorBPFunctionLibrary::TryGetAnchorTransformByHandle(const FOculusXRUInt64& Handle, FTransform& OutTransform, FOculusXRAnchorLocationFlags& OutLocationFlags, EOculusXRAnchorSpace Space)
|
||||
{
|
||||
auto result = OculusXRAnchors::FOculusXRAnchorManager::TryGetAnchorTransform(Handle, OutTransform, OutLocationFlags, Space);
|
||||
return IsAnchorResultSuccess(result);
|
||||
}
|
||||
|
||||
FString UOculusXRAnchorBPFunctionLibrary::AnchorHandleToString(const FOculusXRUInt64 Value)
|
||||
{
|
||||
return FString::Printf(TEXT("%llu"), Value.Value);
|
||||
}
|
||||
|
||||
FString UOculusXRAnchorBPFunctionLibrary::AnchorUUIDToString(const FOculusXRUUID& Value)
|
||||
{
|
||||
return Value.ToString();
|
||||
}
|
||||
|
||||
FOculusXRUUID UOculusXRAnchorBPFunctionLibrary::StringToAnchorUUID(const FString& Value)
|
||||
{
|
||||
// Static size for the max length of the string, two chars per hex digit, 16 digits.
|
||||
checkf(Value.Len() == 32, TEXT("'%s' is not a valid UUID"), *Value);
|
||||
|
||||
ovrpUuid newID;
|
||||
HexToBytes(Value, newID.data);
|
||||
|
||||
return FOculusXRUUID(newID.data);
|
||||
}
|
||||
bool UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(EOculusXRAnchorResult::Type result)
|
||||
{
|
||||
#if OCULUS_HMD_SUPPORTED_PLATFORMS
|
||||
return OVRP_SUCCESS(result);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
const UOculusXRBaseAnchorComponent* UOculusXRAnchorBPFunctionLibrary::GetAnchorComponent(const FOculusXRSpaceQueryResult& QueryResult, EOculusXRSpaceComponentType ComponentType, UObject* Outer)
|
||||
{
|
||||
auto& anchorsModule = FModuleManager::GetModuleChecked<IOculusXRAnchorsModule>("OculusXRAnchors");
|
||||
return anchorsModule.CreateAnchorComponent(QueryResult.Space.Value, ComponentType, Outer);
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRAnchorComponent.h"
|
||||
#include "OculusXRAnchors.h"
|
||||
#include "OculusXRAnchorsModule.h"
|
||||
#include "OculusXRHMD.h"
|
||||
#include "OculusXRAnchorBPFunctionLibrary.h"
|
||||
#include "OculusXRAnchorsPrivate.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
|
||||
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
||||
static TAutoConsoleVariable<int32> CVarOculusXRVerboseAnchorDebugXR(
|
||||
TEXT("ovr.OculusXRVerboseAnchorDebug"),
|
||||
0,
|
||||
TEXT("Enables or disables verbose logging for Oculus anchors.\n")
|
||||
TEXT("<=0: disabled (no printing)\n")
|
||||
TEXT(" 1: enabled (verbose logging)\n"));
|
||||
#endif
|
||||
|
||||
UOculusXRAnchorComponent::UOculusXRAnchorComponent(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
, bUpdateHeadSpaceTransform(true)
|
||||
, AnchorHandle(0)
|
||||
, StorageLocations(0)
|
||||
{
|
||||
AnchorHandle = 0;
|
||||
PrimaryComponentTick.bCanEverTick = true;
|
||||
PrimaryComponentTick.bStartWithTickEnabled = true;
|
||||
PrimaryComponentTick.TickGroup = TG_PostUpdateWork;
|
||||
}
|
||||
|
||||
void UOculusXRAnchorComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
UWorld* World = GetWorld();
|
||||
if (IsValid(World))
|
||||
{
|
||||
APlayerController* PlayerController = World->GetFirstPlayerController();
|
||||
if (IsValid(PlayerController))
|
||||
{
|
||||
PlayerCameraManager = PlayerController->PlayerCameraManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOculusXRAnchorComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
UpdateAnchorTransform();
|
||||
}
|
||||
|
||||
void UOculusXRAnchorComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
Super::EndPlay(EndPlayReason);
|
||||
|
||||
if (HasValidHandle())
|
||||
{
|
||||
EOculusXRAnchorResult::Type AnchorResult;
|
||||
OculusXRAnchors::FOculusXRAnchors::DestroyAnchor(AnchorHandle.GetValue(), AnchorResult);
|
||||
}
|
||||
}
|
||||
|
||||
FOculusXRUInt64 UOculusXRAnchorComponent::GetHandle() const
|
||||
{
|
||||
return AnchorHandle;
|
||||
}
|
||||
|
||||
void UOculusXRAnchorComponent::SetHandle(FOculusXRUInt64 Handle)
|
||||
{
|
||||
AnchorHandle = Handle;
|
||||
}
|
||||
|
||||
bool UOculusXRAnchorComponent::HasValidHandle() const
|
||||
{
|
||||
return AnchorHandle != FOculusXRUInt64(0);
|
||||
}
|
||||
|
||||
FOculusXRUUID UOculusXRAnchorComponent::GetUUID() const
|
||||
{
|
||||
return AnchorUUID;
|
||||
}
|
||||
|
||||
void UOculusXRAnchorComponent::SetUUID(FOculusXRUUID NewUUID)
|
||||
{
|
||||
if (AnchorUUID.IsValidUUID())
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Anchor component already has valid UUID, cannot re-assign a new UUID. Component: %s -- Space: %llu -- UUID: %s"),
|
||||
*GetName(), AnchorHandle.GetValue(), *AnchorUUID.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NewUUID.IsValidUUID())
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("New UUID provided to component is invalid, cannot assign. Component: %s -- Space: %llu"), *GetName(), AnchorHandle.GetValue());
|
||||
return;
|
||||
}
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Assigned new Oculus UUID: %s"), *NewUUID.ToString());
|
||||
|
||||
AnchorUUID = NewUUID;
|
||||
}
|
||||
|
||||
bool UOculusXRAnchorComponent::IsStoredAtLocation(EOculusXRSpaceStorageLocation Location) const
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Anchor UUID: %s - Saved Local: %d - Saved Cloud: %d"),
|
||||
*GetUUID().ToString(),
|
||||
StorageLocations & static_cast<int32>(EOculusXRSpaceStorageLocation::Local),
|
||||
StorageLocations & static_cast<int32>(EOculusXRSpaceStorageLocation::Cloud));
|
||||
|
||||
return (StorageLocations & static_cast<int32>(Location)) > 0;
|
||||
}
|
||||
|
||||
void UOculusXRAnchorComponent::SetStoredLocation(EOculusXRSpaceStorageLocation Location, bool Stored)
|
||||
{
|
||||
if (Stored)
|
||||
{
|
||||
StorageLocations |= static_cast<int32>(Location);
|
||||
}
|
||||
else
|
||||
{
|
||||
StorageLocations = StorageLocations & ~static_cast<int32>(Location);
|
||||
}
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Anchor UUID: %s - Saved Local: %d - Saved Cloud: %d"),
|
||||
*GetUUID().ToString(),
|
||||
StorageLocations & static_cast<int32>(EOculusXRSpaceStorageLocation::Local),
|
||||
StorageLocations & static_cast<int32>(EOculusXRSpaceStorageLocation::Cloud));
|
||||
}
|
||||
|
||||
bool UOculusXRAnchorComponent::IsSaved() const
|
||||
{
|
||||
return StorageLocations > 0;
|
||||
}
|
||||
|
||||
void UOculusXRAnchorComponent::UpdateAnchorTransform() const
|
||||
{
|
||||
if (GetWorld() == nullptr)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Unable to retrieve World Context"));
|
||||
return;
|
||||
}
|
||||
|
||||
AActor* Parent = GetOwner();
|
||||
if (Parent)
|
||||
{
|
||||
if (AnchorHandle.Value)
|
||||
{
|
||||
FTransform OutTransform;
|
||||
if (UOculusXRAnchorBPFunctionLibrary::GetAnchorTransformByHandle(AnchorHandle, OutTransform))
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
// Link only head-space transform update
|
||||
if (bUpdateHeadSpaceTransform && PlayerCameraManager != nullptr)
|
||||
{
|
||||
FTransform MainCameraTransform;
|
||||
MainCameraTransform.SetLocation(PlayerCameraManager->GetCameraLocation());
|
||||
MainCameraTransform.SetRotation(FQuat(PlayerCameraManager->GetCameraRotation()));
|
||||
|
||||
if (!ToWorldSpacePose(MainCameraTransform, OutTransform))
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Display, TEXT("Was not able to transform anchor to world space pose"));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
||||
if (CVarOculusXRVerboseAnchorDebugXR.GetValueOnGameThread() > 0)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Display, TEXT("UpdateAnchor Pos %s"), *OutTransform.GetLocation().ToString());
|
||||
UE_LOG(LogOculusXRAnchors, Display, TEXT("UpdateAnchor Rot %s"), *OutTransform.GetRotation().ToString());
|
||||
}
|
||||
#endif
|
||||
Parent->SetActorLocationAndRotation(OutTransform.GetLocation(), OutTransform.GetRotation(), false, 0, ETeleportType::ResetPhysics);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UOculusXRAnchorComponent::ToWorldSpacePose(FTransform CameraTransform, FTransform& OutTrackingSpaceTransform) const
|
||||
{
|
||||
OculusXRHMD::FOculusXRHMD* OculusXRHMD = OculusXRHMD::FOculusXRHMD::GetOculusXRHMD();
|
||||
if (!OculusXRHMD)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Unable to retrieve OculusXRHMD, cannot calculate anchor world space pose."));
|
||||
return false;
|
||||
}
|
||||
|
||||
OculusXRHMD::FPose MainCameraPose(CameraTransform.GetRotation(), CameraTransform.GetLocation());
|
||||
OculusXRHMD::FPose TrackingSpacePose(OutTrackingSpaceTransform.GetRotation(), OutTrackingSpaceTransform.GetLocation());
|
||||
|
||||
FVector OutHeadPosition;
|
||||
FQuat OutHeadOrientation;
|
||||
const bool bGetPose = OculusXRHMD->GetCurrentPose(OculusXRHMD->HMDDeviceId, OutHeadOrientation, OutHeadPosition);
|
||||
if (!bGetPose)
|
||||
return false;
|
||||
|
||||
OculusXRHMD::FPose HeadPose(OutHeadOrientation, OutHeadPosition);
|
||||
|
||||
OculusXRHMD::FPose poseInHeadSpace = HeadPose.Inverse() * TrackingSpacePose;
|
||||
|
||||
// To world space pose
|
||||
const OculusXRHMD::FPose WorldTrackingSpacePose = MainCameraPose * poseInHeadSpace;
|
||||
|
||||
OutTrackingSpaceTransform.SetLocation(WorldTrackingSpacePose.Position);
|
||||
OutTrackingSpaceTransform.SetRotation(WorldTrackingSpacePose.Orientation);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRAnchorComponents.h"
|
||||
|
||||
#include "OculusXRAnchorBPFunctionLibrary.h"
|
||||
#include "OculusXRAnchorManager.h"
|
||||
#include "OculusXRSpatialAnchorComponent.h"
|
||||
|
||||
bool UOculusXRBaseAnchorComponent::IsComponentEnabled() const
|
||||
{
|
||||
bool OutEnabled;
|
||||
bool OutChangePending;
|
||||
|
||||
auto OutResult = OculusXRAnchors::FOculusXRAnchorManager::GetAnchorComponentStatus(Space, Type, OutEnabled, OutChangePending);
|
||||
return UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OutResult) && OutEnabled;
|
||||
}
|
||||
|
||||
EOculusXRSpaceComponentType UOculusXRBaseAnchorComponent::GetType() const
|
||||
{
|
||||
return Type;
|
||||
}
|
||||
|
||||
uint64 UOculusXRBaseAnchorComponent::GetSpace() const
|
||||
{
|
||||
return Space;
|
||||
}
|
||||
|
||||
bool UOculusXRLocatableAnchorComponent::GetTransform(FTransform& outTransform) const
|
||||
{
|
||||
ensure(IsComponentEnabled());
|
||||
|
||||
if (!UOculusXRAnchorBPFunctionLibrary::GetAnchorTransformByHandle(Space, outTransform))
|
||||
{
|
||||
UE_LOG(LogOculusSpatialAnchor, Warning, TEXT("Fetching transform failed."));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UOculusXRSpaceContainerAnchorComponent::GetUUIDs(TArray<FOculusXRUUID>& outUUIDs) const
|
||||
{
|
||||
ensure(IsComponentEnabled());
|
||||
|
||||
if (!OculusXRAnchors::FOculusXRAnchorManager::GetAnchorContainerUUIDs(Space, outUUIDs))
|
||||
{
|
||||
UE_LOG(LogOculusSpatialAnchor, Warning, TEXT("Fetching container uuids failed."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRAnchorDelegates.h"
|
||||
|
||||
FOculusXRAnchorEventDelegates::FOculusXRSpatialAnchorCreateCompleteDelegate FOculusXRAnchorEventDelegates::OculusSpatialAnchorCreateComplete;
|
||||
|
||||
FOculusXRAnchorEventDelegates::FOculusXRSpaceSetComponentStatusCompleteDelegate FOculusXRAnchorEventDelegates::OculusSpaceSetComponentStatusComplete;
|
||||
|
||||
FOculusXRAnchorEventDelegates::FOculusXRSpaceQueryResultsDelegate FOculusXRAnchorEventDelegates::OculusSpaceQueryResults;
|
||||
|
||||
FOculusXRAnchorEventDelegates::FOculusXRSpaceQueryResultDelegate FOculusXRAnchorEventDelegates::OculusSpaceQueryResult;
|
||||
|
||||
FOculusXRAnchorEventDelegates::FOculusXRSpaceQueryCompleteDelegate FOculusXRAnchorEventDelegates::OculusSpaceQueryComplete;
|
||||
|
||||
FOculusXRAnchorEventDelegates::FOculusXRSpaceSaveCompleteDelegate FOculusXRAnchorEventDelegates::OculusSpaceSaveComplete;
|
||||
|
||||
FOculusXRAnchorEventDelegates::FOculusXRSpaceListSaveCompleteDelegate FOculusXRAnchorEventDelegates::OculusSpaceListSaveComplete;
|
||||
|
||||
FOculusXRAnchorEventDelegates::FOculusXRSpaceEraseCompleteDelegate FOculusXRAnchorEventDelegates::OculusSpaceEraseComplete;
|
||||
|
||||
FOculusXRAnchorEventDelegates::FOculusXRSpaceShareCompleteDelegate FOculusXRAnchorEventDelegates::OculusSpaceShareComplete;
|
||||
|
||||
FOculusXRAnchorEventDelegates::FOculusXRAnchorsDiscoverCompleteDelegate FOculusXRAnchorEventDelegates::OculusAnchorsDiscoverComplete;
|
||||
|
||||
FOculusXRAnchorEventDelegates::FOculusXRAnchorsDiscoverResultsDelegate FOculusXRAnchorEventDelegates::OculusAnchorsDiscoverResults;
|
||||
|
||||
FOculusXRAnchorEventDelegates::FOculusXRAnchorsSaveCompleteDelegate FOculusXRAnchorEventDelegates::OculusAnchorsSaveComplete;
|
||||
|
||||
FOculusXRAnchorEventDelegates::FOculusXRAnchorsEraseCompleteDelegate FOculusXRAnchorEventDelegates::OculusAnchorsEraseComplete;
|
||||
|
||||
FOculusXRAnchorEventDelegates::FOculusXRShareAnchorsCompleteDelegate FOculusXRAnchorEventDelegates::OculusShareAnchorsComplete;
|
||||
@@ -0,0 +1,612 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRAnchorFunctionsOVR.h"
|
||||
#include "OculusXRHMD.h"
|
||||
#include "OculusXRAnchorsModule.h"
|
||||
#include "OculusXRAnchorDelegates.h"
|
||||
#include "OculusXRAnchorBPFunctionLibrary.h"
|
||||
#include "OculusXRAnchorTypesPrivate.h"
|
||||
#include "OculusXRAnchorsUtil.h"
|
||||
|
||||
ovrpSpaceQueryInfo2 ToOvrpSpaceQuery(const FOculusXRSpaceQueryInfo& UEQueryInfo)
|
||||
{
|
||||
static const int32 MaxIdsInFilter = 1024;
|
||||
static const int32 MaxComponentTypesInFilter = 1;
|
||||
|
||||
ovrpSpaceQueryInfo2 Result = {};
|
||||
|
||||
Result.queryType = ovrpSpaceQueryType_Action;
|
||||
Result.actionType = ovrpSpaceQueryActionType_Load;
|
||||
|
||||
Result.maxQuerySpaces = UEQueryInfo.MaxQuerySpaces;
|
||||
Result.timeout = static_cast<double>(UEQueryInfo.Timeout);
|
||||
|
||||
switch (UEQueryInfo.Location)
|
||||
{
|
||||
case EOculusXRSpaceStorageLocation::Invalid:
|
||||
Result.location = ovrpSpaceStorageLocation_Invalid;
|
||||
break;
|
||||
case EOculusXRSpaceStorageLocation::Local:
|
||||
Result.location = ovrpSpaceStorageLocation_Local;
|
||||
break;
|
||||
case EOculusXRSpaceStorageLocation::Cloud:
|
||||
Result.location = ovrpSpaceStorageLocation_Cloud;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (UEQueryInfo.FilterType)
|
||||
{
|
||||
case EOculusXRSpaceQueryFilterType::None:
|
||||
Result.filterType = ovrpSpaceQueryFilterType_None;
|
||||
break;
|
||||
case EOculusXRSpaceQueryFilterType::FilterByIds:
|
||||
Result.filterType = ovrpSpaceQueryFilterType_Ids;
|
||||
break;
|
||||
case EOculusXRSpaceQueryFilterType::FilterByComponentType:
|
||||
Result.filterType = ovrpSpaceQueryFilterType_Components;
|
||||
break;
|
||||
case EOculusXRSpaceQueryFilterType::FilterByGroup:
|
||||
Result.filterType = ovrpSpaceQueryFilterType_GroupUuid;
|
||||
break;
|
||||
}
|
||||
|
||||
Result.IdInfo.numIds = FMath::Min(MaxIdsInFilter, UEQueryInfo.IDFilter.Num());
|
||||
for (int i = 0; i < Result.IdInfo.numIds; ++i)
|
||||
{
|
||||
ovrpUuid OvrUuid;
|
||||
FMemory::Memcpy(OvrUuid.data, UEQueryInfo.IDFilter[i].UUIDBytes);
|
||||
Result.IdInfo.ids[i] = OvrUuid;
|
||||
}
|
||||
|
||||
if (UEQueryInfo.ComponentFilter.Num() > 1)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Query info has more than one component. Using first component only."));
|
||||
}
|
||||
|
||||
Result.componentsInfo.numComponents = FMath::Min(MaxComponentTypesInFilter, UEQueryInfo.ComponentFilter.Num());
|
||||
for (int i = 0; i < Result.componentsInfo.numComponents; ++i)
|
||||
{
|
||||
Result.componentsInfo.components[i] = ConvertToOvrpComponentType(UEQueryInfo.ComponentFilter[i]);
|
||||
}
|
||||
|
||||
FMemory::Memcpy(Result.groupUuidInfo.groupUuid.data, UEQueryInfo.GroupUUIDFilter.UUIDBytes);
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOVR::CreateAnchor(const FTransform& InTransform, uint64& OutRequestId, const FTransform& CameraTransform)
|
||||
{
|
||||
OculusXRHMD::FOculusXRHMD* HMD = OculusXRHMD::FOculusXRHMD::GetOculusXRHMD();
|
||||
if (!HMD)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("FOculusAnchorManager::CreateAnchor failed to retrieve HMD."));
|
||||
return EOculusXRAnchorResult::Failure;
|
||||
}
|
||||
|
||||
ovrpTrackingOrigin TrackingOriginType;
|
||||
ovrpPosef Posef;
|
||||
double Time = 0;
|
||||
|
||||
const FTransform TrackingToWorld = HMD->GetLastTrackingToWorld();
|
||||
|
||||
// convert to tracking space
|
||||
const FQuat TrackingSpaceOrientation = TrackingToWorld.Inverse().TransformRotation(InTransform.Rotator().Quaternion());
|
||||
const FVector TrackingSpacePosition = TrackingToWorld.Inverse().TransformPosition(InTransform.GetLocation());
|
||||
|
||||
const OculusXRHMD::FPose TrackingSpacePose(TrackingSpaceOrientation, TrackingSpacePosition);
|
||||
|
||||
#if WITH_EDITOR
|
||||
// Link only head space position update
|
||||
FVector OutHeadPosition;
|
||||
FQuat OutHeadOrientation;
|
||||
const bool bGetPose = HMD->GetCurrentPose(HMD->HMDDeviceId, OutHeadOrientation, OutHeadPosition);
|
||||
if (!bGetPose)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("FOculusAnchorManager::CreateAnchor failed to get current headset pose."));
|
||||
return EOculusXRAnchorResult::Failure;
|
||||
}
|
||||
|
||||
OculusXRHMD::FPose HeadPose(OutHeadOrientation, OutHeadPosition);
|
||||
|
||||
OculusXRHMD::FPose MainCameraPose(CameraTransform.GetRotation(), CameraTransform.GetLocation());
|
||||
OculusXRHMD::FPose PoseInHeadSpace = MainCameraPose.Inverse() * TrackingSpacePose;
|
||||
|
||||
// To world space pose
|
||||
OculusXRHMD::FPose WorldPose = HeadPose * PoseInHeadSpace;
|
||||
|
||||
const bool bConverted = HMD->ConvertPose(WorldPose, Posef);
|
||||
#else
|
||||
const bool bConverted = HMD->ConvertPose(TrackingSpacePose, Posef);
|
||||
#endif
|
||||
|
||||
if (!bConverted)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("FOculusAnchorManager::CreateAnchor failed to convert pose."));
|
||||
return EOculusXRAnchorResult::Failure;
|
||||
}
|
||||
|
||||
FOculusXRHMDModule::GetPluginWrapper().GetTrackingOriginType2(&TrackingOriginType);
|
||||
FOculusXRHMDModule::GetPluginWrapper().GetTimeInSeconds(&Time);
|
||||
|
||||
const ovrpSpatialAnchorCreateInfo SpatialAnchorCreateInfo = {
|
||||
TrackingOriginType,
|
||||
Posef,
|
||||
Time
|
||||
};
|
||||
|
||||
const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().CreateSpatialAnchor(&SpatialAnchorCreateInfo, &OutRequestId);
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("CreateAnchor Request ID: %llu"), OutRequestId);
|
||||
|
||||
if (OVRP_FAILURE(Result))
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Error, TEXT("FOculusAnchorManager::CreateAnchor failed. Result: %d"), Result);
|
||||
}
|
||||
|
||||
return OculusXRAnchors::GetResultFromOVRResult(Result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOVR::DestroyAnchor(uint64 AnchorHandle)
|
||||
{
|
||||
const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().DestroySpace(static_cast<ovrpSpace*>(&AnchorHandle));
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("DestroyAnchor -- ID: %llu"), AnchorHandle);
|
||||
|
||||
return OculusXRAnchors::GetResultFromOVRResult(Result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOVR::TryGetAnchorTransform(uint64 AnchorHandle, FTransform& OutTransform, FOculusXRAnchorLocationFlags& OutLocationFlags, EOculusXRAnchorSpace Space)
|
||||
{
|
||||
OculusXRHMD::FOculusXRHMD* OutHMD = OculusXRHMD::FOculusXRHMD::GetOculusXRHMD();
|
||||
if (!OutHMD)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Unable to retrieve OculusXRHMD, cannot calculate anchor transform."));
|
||||
return EOculusXRAnchorResult::Failure_InvalidOperation;
|
||||
}
|
||||
|
||||
ovrpTrackingOrigin ovrpOrigin = ovrpTrackingOrigin_EyeLevel;
|
||||
const bool bTrackingOriginSuccess = OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetTrackingOriginType2(&ovrpOrigin));
|
||||
if (!bTrackingOriginSuccess)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Unable to get tracking origin, cannot calculate anchor transform."));
|
||||
return EOculusXRAnchorResult::Failure_InvalidOperation;
|
||||
}
|
||||
|
||||
OutTransform = FTransform::Identity;
|
||||
OutLocationFlags = FOculusXRAnchorLocationFlags(0);
|
||||
|
||||
const ovrpUInt64 ovrpSpace = AnchorHandle;
|
||||
ovrpSpaceLocationf ovrpSpaceLocation{};
|
||||
|
||||
const ovrpResult result = FOculusXRHMDModule::GetPluginWrapper().LocateSpace2(&ovrpSpaceLocation, &ovrpSpace, ovrpOrigin);
|
||||
if (OVRP_SUCCESS(result))
|
||||
{
|
||||
OutLocationFlags = FOculusXRAnchorLocationFlags(ovrpSpaceLocation.locationFlags);
|
||||
if (OutLocationFlags.IsValid())
|
||||
{
|
||||
OculusXRHMD::FPose Pose;
|
||||
OutHMD->ConvertPose(ovrpSpaceLocation.pose, Pose);
|
||||
switch (Space)
|
||||
{
|
||||
case EOculusXRAnchorSpace::World:
|
||||
{
|
||||
const FTransform trackingToWorld = OutHMD->GetLastTrackingToWorld();
|
||||
OutTransform.SetLocation(trackingToWorld.TransformPosition(Pose.Position));
|
||||
OutTransform.SetRotation(FRotator(trackingToWorld.TransformRotation(FQuat(Pose.Orientation))).Quaternion());
|
||||
}
|
||||
break;
|
||||
case EOculusXRAnchorSpace::Tracking:
|
||||
{
|
||||
OutTransform.SetLocation(Pose.Position);
|
||||
OutTransform.SetRotation(FRotator(FQuat(Pose.Orientation)).Quaternion());
|
||||
}
|
||||
break;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return EOculusXRAnchorResult::Failure_OperationFailed;
|
||||
}
|
||||
}
|
||||
|
||||
return OculusXRAnchors::GetResultFromOVRResult(result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOVR::SetAnchorComponentStatus(uint64 AnchorHandle, EOculusXRSpaceComponentType ComponentType, bool Enable, float Timeout, uint64& OutRequestId)
|
||||
{
|
||||
ovrpSpaceComponentType ovrpType = ConvertToOvrpComponentType(ComponentType);
|
||||
const ovrpUInt64 OVRPSpace = AnchorHandle;
|
||||
|
||||
// validate existing status
|
||||
ovrpBool isEnabled = false;
|
||||
ovrpBool changePending = false;
|
||||
const ovrpResult getComponentStatusResult = FOculusXRHMDModule::GetPluginWrapper().GetSpaceComponentStatus(&OVRPSpace, ovrpType, &isEnabled, &changePending);
|
||||
|
||||
bool isStatusChangingOrSame = (static_cast<bool>(isEnabled) == Enable && !changePending) || (static_cast<bool>(isEnabled) != Enable && changePending);
|
||||
if (OVRP_SUCCESS(getComponentStatusResult) && isStatusChangingOrSame)
|
||||
{
|
||||
return EOculusXRAnchorResult::Success;
|
||||
}
|
||||
|
||||
// set status
|
||||
const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().SetSpaceComponentStatus(
|
||||
&OVRPSpace,
|
||||
ovrpType,
|
||||
Enable,
|
||||
Timeout,
|
||||
&OutRequestId);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("SetSpaceComponentStatus Request ID: %llu"), OutRequestId);
|
||||
|
||||
return OculusXRAnchors::GetResultFromOVRResult(Result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOVR::GetAnchorComponentStatus(uint64 AnchorHandle, EOculusXRSpaceComponentType ComponentType, bool& OutEnabled, bool& OutChangePending)
|
||||
{
|
||||
const ovrpUInt64 OVRPSpace = AnchorHandle;
|
||||
ovrpBool OutOvrpEnabled = ovrpBool_False;
|
||||
ovrpBool OutOvrpChangePending = ovrpBool_False;
|
||||
|
||||
ovrpSpaceComponentType ovrpType = ConvertToOvrpComponentType(ComponentType);
|
||||
|
||||
const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceComponentStatus(
|
||||
&OVRPSpace,
|
||||
ovrpType,
|
||||
&OutOvrpEnabled,
|
||||
&OutOvrpChangePending);
|
||||
|
||||
OutEnabled = (OutOvrpEnabled == ovrpBool_True);
|
||||
OutChangePending = (OutOvrpChangePending == ovrpBool_True);
|
||||
|
||||
return OculusXRAnchors::GetResultFromOVRResult(Result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOVR::GetSupportedAnchorComponents(uint64 AnchorHandle, TArray<EOculusXRSpaceComponentType>& OutSupportedTypes)
|
||||
{
|
||||
ovrpSpace ovrSpace = AnchorHandle;
|
||||
TArray<ovrpSpaceComponentType> ovrComponentTypes;
|
||||
ovrpUInt32 input = 0;
|
||||
ovrpUInt32 output = 0;
|
||||
|
||||
ovrpResult enumerateResult = FOculusXRHMDModule::GetPluginWrapper().EnumerateSpaceSupportedComponents(&ovrSpace, input, &output, nullptr);
|
||||
if (!OVRP_SUCCESS(enumerateResult))
|
||||
{
|
||||
return OculusXRAnchors::GetResultFromOVRResult(enumerateResult);
|
||||
}
|
||||
|
||||
input = output;
|
||||
ovrComponentTypes.SetNumZeroed(output);
|
||||
|
||||
enumerateResult = FOculusXRHMDModule::GetPluginWrapper().EnumerateSpaceSupportedComponents(&ovrSpace, input, &output, ovrComponentTypes.GetData());
|
||||
if (!OVRP_SUCCESS(enumerateResult))
|
||||
{
|
||||
return OculusXRAnchors::GetResultFromOVRResult(enumerateResult);
|
||||
}
|
||||
|
||||
OutSupportedTypes.SetNumZeroed(ovrComponentTypes.Num());
|
||||
for (int i = 0; i < ovrComponentTypes.Num(); ++i)
|
||||
{
|
||||
OutSupportedTypes[i] = ConvertToUEComponentType(ovrComponentTypes[i]);
|
||||
}
|
||||
|
||||
return OculusXRAnchors::GetResultFromOVRResult(enumerateResult);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOVR::GetAnchorContainerUUIDs(uint64 AnchorHandle, TArray<FOculusXRUUID>& OutUUIDs)
|
||||
{
|
||||
TArray<ovrpUuid> ovrUuidArray;
|
||||
|
||||
// Get the number of elements in the container
|
||||
ovrpSpaceContainer ovrSpaceContainer = {};
|
||||
ovrSpaceContainer.uuidCapacityInput = 0;
|
||||
ovrSpaceContainer.uuidCountOutput = 0;
|
||||
ovrSpaceContainer.uuids = nullptr;
|
||||
ovrpResult result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceContainer(&AnchorHandle, &ovrSpaceContainer);
|
||||
if (OVRP_FAILURE(result))
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to get space container %d"), result);
|
||||
return OculusXRAnchors::GetResultFromOVRResult(result);
|
||||
}
|
||||
|
||||
// Retrieve the actual array of UUIDs
|
||||
ovrUuidArray.SetNum(ovrSpaceContainer.uuidCountOutput);
|
||||
ovrSpaceContainer.uuidCapacityInput = ovrSpaceContainer.uuidCountOutput;
|
||||
ovrSpaceContainer.uuids = ovrUuidArray.GetData();
|
||||
|
||||
result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceContainer(&AnchorHandle, &ovrSpaceContainer);
|
||||
if (OVRP_FAILURE(result))
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to get space container %d"), result);
|
||||
return OculusXRAnchors::GetResultFromOVRResult(result);
|
||||
}
|
||||
|
||||
// Write out the remaining UUIDs
|
||||
OutUUIDs.Reserve(ovrUuidArray.Num());
|
||||
for (auto& it : ovrUuidArray)
|
||||
{
|
||||
OutUUIDs.Add(FOculusXRUUID(it.data));
|
||||
}
|
||||
|
||||
return EOculusXRAnchorResult::Success;
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOVR::SaveAnchor(uint64 AnchorHandle,
|
||||
EOculusXRSpaceStorageLocation StorageLocation,
|
||||
EOculusXRSpaceStoragePersistenceMode StoragePersistenceMode, uint64& OutRequestId)
|
||||
{
|
||||
ovrpSpaceStorageLocation OvrpStorageLocation = ovrpSpaceStorageLocation_Local;
|
||||
switch (StorageLocation)
|
||||
{
|
||||
case EOculusXRSpaceStorageLocation::Invalid:
|
||||
OvrpStorageLocation = ovrpSpaceStorageLocation_Invalid;
|
||||
break;
|
||||
case EOculusXRSpaceStorageLocation::Local:
|
||||
OvrpStorageLocation = ovrpSpaceStorageLocation_Local;
|
||||
break;
|
||||
case EOculusXRSpaceStorageLocation::Cloud:
|
||||
OvrpStorageLocation = ovrpSpaceStorageLocation_Cloud;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ovrpSpaceStoragePersistenceMode OvrpStoragePersistenceMode = ovrpSpaceStoragePersistenceMode_Invalid;
|
||||
switch (StoragePersistenceMode)
|
||||
{
|
||||
case EOculusXRSpaceStoragePersistenceMode::Invalid:
|
||||
OvrpStoragePersistenceMode = ovrpSpaceStoragePersistenceMode_Invalid;
|
||||
break;
|
||||
case EOculusXRSpaceStoragePersistenceMode::Indefinite:
|
||||
OvrpStoragePersistenceMode = ovrpSpaceStoragePersistenceMode_Indefinite;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().SaveSpace(&AnchorHandle, OvrpStorageLocation, OvrpStoragePersistenceMode, &OutRequestId);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Saving anchor with, ID: %llu -- Location: %d -- Persistence: %d -- OutID: %llu"), AnchorHandle, OvrpStorageLocation, OvrpStoragePersistenceMode, OutRequestId);
|
||||
|
||||
if (OVRP_FAILURE(Result))
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("FOculusXRHMD::SaveAnchor failed with, ID: %llu -- Location: %d -- Persistence: %d"), AnchorHandle, OvrpStorageLocation, OvrpStoragePersistenceMode);
|
||||
}
|
||||
|
||||
return OculusXRAnchors::GetResultFromOVRResult(Result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOVR::SaveAnchorList(const TArray<uint64>& AnchorHandles, EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId)
|
||||
{
|
||||
ovrpSpaceStorageLocation OvrpStorageLocation = ovrpSpaceStorageLocation_Local;
|
||||
switch (StorageLocation)
|
||||
{
|
||||
case EOculusXRSpaceStorageLocation::Invalid:
|
||||
OvrpStorageLocation = ovrpSpaceStorageLocation_Invalid;
|
||||
break;
|
||||
case EOculusXRSpaceStorageLocation::Local:
|
||||
OvrpStorageLocation = ovrpSpaceStorageLocation_Local;
|
||||
break;
|
||||
case EOculusXRSpaceStorageLocation::Cloud:
|
||||
OvrpStorageLocation = ovrpSpaceStorageLocation_Cloud;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().SaveSpaceList(AnchorHandles.GetData(), AnchorHandles.Num(), OvrpStorageLocation, &OutRequestId);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Saving space list: Location: %d -- OutID: %llu"), OvrpStorageLocation, OutRequestId);
|
||||
for (auto& it : AnchorHandles)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("\tSpaceID: %llu"), it);
|
||||
}
|
||||
|
||||
if (OVRP_FAILURE(Result))
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("SaveSpaceList failed -- Result: %d"), Result);
|
||||
}
|
||||
|
||||
return OculusXRAnchors::GetResultFromOVRResult(Result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOVR::SaveAnchors(const TArray<uint64>& AnchorHandles, uint64& OutRequestId)
|
||||
{
|
||||
if (AnchorHandles.Num() == 0)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("FOculusXRAnchorFunctionsOVR::SaveAnchors has empty handle array"));
|
||||
}
|
||||
|
||||
const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().SaveSpaces(AnchorHandles.Num(), AnchorHandles.GetData(), &OutRequestId);
|
||||
|
||||
if (!OVRP_SUCCESS(Result))
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Error, TEXT("FOculusXRAnchorFunctionsOVR::SaveAnchors failed, result: %d"), Result);
|
||||
}
|
||||
|
||||
return OculusXRAnchors::GetResultFromOVRResult(Result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOVR::DiscoverAnchors(const FOculusXRSpaceDiscoveryInfo& DiscoveryInfo, uint64& OutRequestId)
|
||||
{
|
||||
uint32 FiltersCount = (uint32)DiscoveryInfo.Filters.Num();
|
||||
ovrpSpaceDiscoveryInfo OvrDiscoveryInfo = {};
|
||||
OvrDiscoveryInfo.FilterCount = FiltersCount;
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Display, TEXT("Staring discovery with %d filter(s)"), FiltersCount);
|
||||
|
||||
TArray<const ovrpSpaceDiscoveryFilterHeader*> filters;
|
||||
filters.SetNumZeroed(FiltersCount);
|
||||
|
||||
for (uint32 i = 0; i < FiltersCount; ++i)
|
||||
{
|
||||
ensure(DiscoveryInfo.Filters[i] != nullptr);
|
||||
filters[i] = DiscoveryInfo.Filters[i]->GenerateOVRPFilter();
|
||||
}
|
||||
|
||||
OvrDiscoveryInfo.Filters = filters.GetData();
|
||||
const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().DiscoverSpaces(&OvrDiscoveryInfo, &OutRequestId);
|
||||
|
||||
if (!OVRP_SUCCESS(Result))
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Error, TEXT("FOculusXRAnchorFunctionsOVR::DiscoverAnchors failed -- Result: %d"), Result);
|
||||
}
|
||||
|
||||
return OculusXRAnchors::GetResultFromOVRResult(Result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOVR::QueryAnchors(const FOculusXRSpaceQueryInfo& QueryInfo, uint64& OutRequestId)
|
||||
{
|
||||
ovrpResult QuerySpacesResult = ovrpFailure;
|
||||
|
||||
ovrpSpaceQueryInfo2 ovrQueryInfo = ToOvrpSpaceQuery(QueryInfo);
|
||||
QuerySpacesResult = FOculusXRHMDModule::GetPluginWrapper().QuerySpaces2(&ovrQueryInfo, &OutRequestId);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Query Spaces\n ovrpSpaceQueryInfo:\n\tQueryType: %d\n\tMaxQuerySpaces: %d\n\tTimeout: %f\n\tLocation: %d\n\tActionType: %d\n\tFilterType: %d\n\n\tRequest ID: %llu"),
|
||||
ovrQueryInfo.queryType, ovrQueryInfo.maxQuerySpaces, (float)ovrQueryInfo.timeout, ovrQueryInfo.location, ovrQueryInfo.actionType, ovrQueryInfo.filterType, OutRequestId);
|
||||
|
||||
if (QueryInfo.FilterType == EOculusXRSpaceQueryFilterType::FilterByIds)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Query contains %d UUIDs"), QueryInfo.IDFilter.Num());
|
||||
for (auto& it : QueryInfo.IDFilter)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("UUID: %s"), *it.ToString());
|
||||
}
|
||||
}
|
||||
else if (QueryInfo.FilterType == EOculusXRSpaceQueryFilterType::FilterByComponentType)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Query contains %d Component Types"), QueryInfo.ComponentFilter.Num());
|
||||
for (auto& it : QueryInfo.ComponentFilter)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ComponentType: %s"), *UEnum::GetValueAsString(it));
|
||||
}
|
||||
}
|
||||
else if (QueryInfo.FilterType == EOculusXRSpaceQueryFilterType::FilterByGroup)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Query contains group filter - UUID: %s"), *QueryInfo.GroupUUIDFilter.ToString());
|
||||
}
|
||||
|
||||
return OculusXRAnchors::GetResultFromOVRResult(QuerySpacesResult);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOVR::ShareAnchors(const TArray<uint64>& AchorHandles, const TArray<uint64>& UserIds, uint64& OutRequestId)
|
||||
{
|
||||
TArray<const char*> stringStorage;
|
||||
TArray<ovrpUser> OvrpUsers;
|
||||
for (const auto& UserId : UserIds)
|
||||
{
|
||||
ovrpUser OvrUser;
|
||||
ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().CreateSpaceUser(&UserId, &OvrUser);
|
||||
if (OVRP_FAILURE(Result))
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to create space user from ID - %llu"), UserId);
|
||||
continue;
|
||||
}
|
||||
|
||||
OvrpUsers.Add(OvrUser);
|
||||
}
|
||||
|
||||
const ovrpResult ShareSpacesResult = FOculusXRHMDModule::GetPluginWrapper().ShareSpaces(AchorHandles.GetData(), AchorHandles.Num(), OvrpUsers.GetData(), OvrpUsers.Num(), &OutRequestId);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Sharing space list -- OutID: %llu"), OutRequestId);
|
||||
for (auto& User : OvrpUsers)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("\tOvrpUser: %llu"), User);
|
||||
ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().DestroySpaceUser(&User);
|
||||
if (OVRP_FAILURE(Result))
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Failed to destroy space user: %llu"), User);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& it : AchorHandles)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("\tSpaceID: %llu"), it);
|
||||
}
|
||||
|
||||
return OculusXRAnchors::GetResultFromOVRResult(ShareSpacesResult);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOVR::ShareAnchors(const TArray<uint64>& AnchorHandles, const TArray<FOculusXRUUID>& Groups, uint64& OutRequestId)
|
||||
{
|
||||
TArray<ovrpUuid> groupUuids;
|
||||
groupUuids.Reserve(Groups.Num());
|
||||
for (auto& it : Groups)
|
||||
{
|
||||
ovrpUuid uuid;
|
||||
FMemory::Memcpy(uuid.data, it.UUIDBytes);
|
||||
groupUuids.Add(uuid);
|
||||
}
|
||||
|
||||
TSharedPtr<ovrpShareSpacesGroupRecipientInfo> groupRecipientInfo = MakeShared<ovrpShareSpacesGroupRecipientInfo>();
|
||||
groupRecipientInfo->GroupCount = groupUuids.Num();
|
||||
groupRecipientInfo->GroupUuids = groupUuids.GetData();
|
||||
|
||||
ovrpShareSpacesInfo shareInfo;
|
||||
shareInfo.SpaceCount = AnchorHandles.Num();
|
||||
shareInfo.Spaces = (ovrpSpace*)AnchorHandles.GetData();
|
||||
shareInfo.RecipientType = ovrpShareSpacesRecipientType_Group;
|
||||
shareInfo.RecipientInfo = groupRecipientInfo.Get();
|
||||
|
||||
ovrpResult result = FOculusXRHMDModule::GetPluginWrapper().ShareSpaces2(&shareInfo, &OutRequestId);
|
||||
|
||||
return OculusXRAnchors::GetResultFromOVRResult(result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOVR::EraseAnchor(uint64 AnchorHandle,
|
||||
EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId)
|
||||
{
|
||||
ovrpSpaceStorageLocation ovrpStorageLocation = ovrpSpaceStorageLocation_Local;
|
||||
switch (StorageLocation)
|
||||
{
|
||||
case EOculusXRSpaceStorageLocation::Invalid:
|
||||
ovrpStorageLocation = ovrpSpaceStorageLocation_Invalid;
|
||||
break;
|
||||
case EOculusXRSpaceStorageLocation::Local:
|
||||
ovrpStorageLocation = ovrpSpaceStorageLocation_Local;
|
||||
break;
|
||||
case EOculusXRSpaceStorageLocation::Cloud:
|
||||
ovrpStorageLocation = ovrpSpaceStorageLocation_Cloud;
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
|
||||
ovrpUInt64 OvrpOutRequestId = 0;
|
||||
const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().EraseSpace(&AnchorHandle, ovrpStorageLocation, &OvrpOutRequestId);
|
||||
memcpy(&OutRequestId, &OvrpOutRequestId, sizeof(uint64));
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Log, TEXT("Erasing anchor -- Handle: %llu -- Location: %d -- OutID: %llu"), AnchorHandle, ovrpStorageLocation, OvrpOutRequestId);
|
||||
|
||||
return OculusXRAnchors::GetResultFromOVRResult(Result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOVR::EraseAnchors(const TArray<FOculusXRUInt64>& AnchorHandles, const TArray<FOculusXRUUID>& UUIDs, uint64& OutRequestId)
|
||||
{
|
||||
if (AnchorHandles.IsEmpty() && UUIDs.IsEmpty())
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("FOculusXRAnchorFunctionsOVR::EraseAnchors - You cannot have an empty handle and uuid array. At least one array must have elements."));
|
||||
return EOculusXRAnchorResult::Failure_InvalidParameter;
|
||||
}
|
||||
|
||||
TArray<ovrpSpace> ovrpHandles;
|
||||
for (auto& handle : AnchorHandles)
|
||||
{
|
||||
ovrpHandles.Add(handle.GetValue());
|
||||
}
|
||||
|
||||
TArray<ovrpUuid> ovrpUUIDs;
|
||||
for (auto& id : UUIDs)
|
||||
{
|
||||
ovrpUuid OvrUuid;
|
||||
FMemory::Memcpy(OvrUuid.data, id.UUIDBytes);
|
||||
ovrpUUIDs.Add(OvrUuid);
|
||||
}
|
||||
|
||||
const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().EraseSpaces(ovrpHandles.Num(), ovrpHandles.GetData(), ovrpUUIDs.Num(), ovrpUUIDs.GetData(), &OutRequestId);
|
||||
|
||||
if (!OVRP_SUCCESS(Result))
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Error, TEXT("FOculusXRAnchorFunctionsOVR::EraseAnchors failed -- Result: %d"), Result);
|
||||
}
|
||||
|
||||
return OculusXRAnchors::GetResultFromOVRResult(Result);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OculusXRAnchorFunctions.h"
|
||||
|
||||
class OCULUSXRANCHORS_API FOculusXRAnchorFunctionsOVR : public IOculusXRAnchorFunctions
|
||||
{
|
||||
public:
|
||||
virtual EOculusXRAnchorResult::Type CreateAnchor(const FTransform& InTransform, uint64& OutRequestId, const FTransform& CameraTransform) override;
|
||||
virtual EOculusXRAnchorResult::Type DestroyAnchor(uint64 AnchorHandle) override;
|
||||
|
||||
virtual EOculusXRAnchorResult::Type TryGetAnchorTransform(uint64 AnchorHandle, FTransform& OutTransform, FOculusXRAnchorLocationFlags& OutLocationFlags, EOculusXRAnchorSpace Space) override;
|
||||
virtual EOculusXRAnchorResult::Type SetAnchorComponentStatus(uint64 AnchorHandle, EOculusXRSpaceComponentType ComponentType, bool Enable, float Timeout, uint64& OutRequestId) override;
|
||||
virtual EOculusXRAnchorResult::Type GetAnchorComponentStatus(uint64 AnchorHandle, EOculusXRSpaceComponentType ComponentType, bool& OutEnabled, bool& OutChangePending) override;
|
||||
virtual EOculusXRAnchorResult::Type GetSupportedAnchorComponents(uint64 AnchorHandle, TArray<EOculusXRSpaceComponentType>& OutSupportedTypes) override;
|
||||
virtual EOculusXRAnchorResult::Type GetAnchorContainerUUIDs(uint64 AnchorHandle, TArray<FOculusXRUUID>& OutUUIDs) override;
|
||||
|
||||
virtual EOculusXRAnchorResult::Type SaveAnchor(uint64 AnchorHandle, EOculusXRSpaceStorageLocation StorageLocation, EOculusXRSpaceStoragePersistenceMode StoragePersistenceMode, uint64& OutRequestId) override;
|
||||
virtual EOculusXRAnchorResult::Type SaveAnchorList(const TArray<uint64>& AnchorHandles, EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId) override;
|
||||
virtual EOculusXRAnchorResult::Type SaveAnchors(const TArray<uint64>& AnchorHandles, uint64& OutRequestId) override;
|
||||
|
||||
virtual EOculusXRAnchorResult::Type DiscoverAnchors(const FOculusXRSpaceDiscoveryInfo& DiscoveryInfo, uint64& OutRequestId) override;
|
||||
virtual EOculusXRAnchorResult::Type QueryAnchors(const FOculusXRSpaceQueryInfo& QueryInfo, uint64& OutRequestId) override;
|
||||
virtual EOculusXRAnchorResult::Type ShareAnchors(const TArray<uint64>& AnchorHandles, const TArray<uint64>& UserIds, uint64& OutRequestId) override;
|
||||
virtual EOculusXRAnchorResult::Type ShareAnchors(const TArray<uint64>& AnchorHandles, const TArray<FOculusXRUUID>& Groups, uint64& OutRequestId) override;
|
||||
|
||||
virtual EOculusXRAnchorResult::Type EraseAnchor(uint64 AnchorHandle, EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId) override;
|
||||
virtual EOculusXRAnchorResult::Type EraseAnchors(const TArray<FOculusXRUInt64>& AnchorHandles, const TArray<FOculusXRUUID>& UUIDs, uint64& OutRequestId) override;
|
||||
};
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRAnchorFunctionsOpenXR.h"
|
||||
#include "OculusXRHMD.h"
|
||||
#include "OculusXRAnchorsModule.h"
|
||||
#include "OculusXRAnchorDelegates.h"
|
||||
#include "OculusXRAnchorBPFunctionLibrary.h"
|
||||
#include "OculusXRAnchorTypesPrivate.h"
|
||||
#include "OculusXRAnchorsUtil.h"
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOpenXR::CreateAnchor(const FTransform& InTransform, uint64& OutRequestId, const FTransform& CameraTransform)
|
||||
{
|
||||
auto result = FOculusXRAnchorsModule::Get().GetXrAnchors()->CreateSpatialAnchor(InTransform, OutRequestId, CameraTransform);
|
||||
return OculusXRAnchors::GetResultFromXrResult(result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOpenXR::DestroyAnchor(uint64 AnchorHandle)
|
||||
{
|
||||
auto result = FOculusXRAnchorsModule::Get().GetXrAnchors()->DestroySpatialAnchor(AnchorHandle);
|
||||
return OculusXRAnchors::GetResultFromXrResult(result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOpenXR::TryGetAnchorTransform(uint64 AnchorHandle, FTransform& OutTransform, FOculusXRAnchorLocationFlags& OutLocationFlags, EOculusXRAnchorSpace Space)
|
||||
{
|
||||
auto result = FOculusXRAnchorsModule::Get().GetXrAnchors()->TryGetAnchorTransform(AnchorHandle, OutTransform, OutLocationFlags, Space);
|
||||
return OculusXRAnchors::GetResultFromXrResult(result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOpenXR::SetAnchorComponentStatus(uint64 AnchorHandle, EOculusXRSpaceComponentType ComponentType, bool Enable, float Timeout, uint64& OutRequestId)
|
||||
{
|
||||
auto result = FOculusXRAnchorsModule::Get().GetXrAnchors()->SetAnchorComponentStatus(AnchorHandle, ComponentType, Enable, Timeout, OutRequestId);
|
||||
return OculusXRAnchors::GetResultFromXrResult(result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOpenXR::GetAnchorComponentStatus(uint64 AnchorHandle, EOculusXRSpaceComponentType ComponentType, bool& OutEnabled, bool& OutChangePending)
|
||||
{
|
||||
auto result = FOculusXRAnchorsModule::Get().GetXrAnchors()->GetAnchorComponentStatus(AnchorHandle, ComponentType, OutEnabled, OutChangePending);
|
||||
return OculusXRAnchors::GetResultFromXrResult(result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOpenXR::GetSupportedAnchorComponents(uint64 AnchorHandle, TArray<EOculusXRSpaceComponentType>& OutSupportedTypes)
|
||||
{
|
||||
auto result = FOculusXRAnchorsModule::Get().GetXrAnchors()->GetSupportedAnchorComponents(AnchorHandle, OutSupportedTypes);
|
||||
return OculusXRAnchors::GetResultFromXrResult(result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOpenXR::GetAnchorContainerUUIDs(uint64 AnchorHandle, TArray<FOculusXRUUID>& OutUUIDs)
|
||||
{
|
||||
auto result = FOculusXRAnchorsModule::Get().GetXrAnchors()->GetAnchorContainerUUIDs(AnchorHandle, OutUUIDs);
|
||||
return OculusXRAnchors::GetResultFromXrResult(result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOpenXR::SaveAnchor(uint64 AnchorHandle,
|
||||
EOculusXRSpaceStorageLocation StorageLocation,
|
||||
EOculusXRSpaceStoragePersistenceMode StoragePersistenceMode, uint64& OutRequestId)
|
||||
{
|
||||
auto result = FOculusXRAnchorsModule::Get().GetXrAnchors()->SaveAnchor(AnchorHandle, StorageLocation, StoragePersistenceMode, OutRequestId);
|
||||
return OculusXRAnchors::GetResultFromXrResult(result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOpenXR::SaveAnchorList(const TArray<uint64>& AnchorHandles, EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId)
|
||||
{
|
||||
auto result = FOculusXRAnchorsModule::Get().GetXrAnchors()->SaveAnchorList(AnchorHandles, StorageLocation, OutRequestId);
|
||||
return OculusXRAnchors::GetResultFromXrResult(result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOpenXR::SaveAnchors(const TArray<uint64>& AnchorHandles, uint64& OutRequestId)
|
||||
{
|
||||
auto result = FOculusXRAnchorsModule::Get().GetXrAnchors()->SaveAnchors(AnchorHandles, OutRequestId);
|
||||
return OculusXRAnchors::GetResultFromXrResult(result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOpenXR::DiscoverAnchors(const FOculusXRSpaceDiscoveryInfo& DiscoveryInfo, uint64& OutRequestId)
|
||||
{
|
||||
auto result = FOculusXRAnchorsModule::Get().GetXrAnchors()->DiscoverAnchors(DiscoveryInfo, OutRequestId);
|
||||
return OculusXRAnchors::GetResultFromXrResult(result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOpenXR::QueryAnchors(const FOculusXRSpaceQueryInfo& QueryInfo, uint64& OutRequestId)
|
||||
{
|
||||
auto result = FOculusXRAnchorsModule::Get().GetXrAnchors()->QueryAnchors(QueryInfo, OutRequestId);
|
||||
return OculusXRAnchors::GetResultFromXrResult(result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOpenXR::ShareAnchors(const TArray<uint64>& AnchorHandles, const TArray<uint64>& UserIds, uint64& OutRequestId)
|
||||
{
|
||||
auto result = FOculusXRAnchorsModule::Get().GetXrAnchors()->ShareAnchors(AnchorHandles, UserIds, OutRequestId);
|
||||
return OculusXRAnchors::GetResultFromXrResult(result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOpenXR::ShareAnchors(const TArray<uint64>& AnchorHandles, const TArray<FOculusXRUUID>& Groups, uint64& OutRequestId)
|
||||
{
|
||||
auto result = FOculusXRAnchorsModule::Get().GetXrAnchors()->ShareAnchorsWithGroups(AnchorHandles, Groups, OutRequestId);
|
||||
return OculusXRAnchors::GetResultFromXrResult(result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOpenXR::EraseAnchor(uint64 AnchorHandle,
|
||||
EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId)
|
||||
{
|
||||
auto result = FOculusXRAnchorsModule::Get().GetXrAnchors()->EraseAnchor(AnchorHandle, StorageLocation, OutRequestId);
|
||||
return OculusXRAnchors::GetResultFromXrResult(result);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorFunctionsOpenXR::EraseAnchors(const TArray<FOculusXRUInt64>& AnchorHandles, const TArray<FOculusXRUUID>& UUIDs, uint64& OutRequestId)
|
||||
{
|
||||
auto result = FOculusXRAnchorsModule::Get().GetXrAnchors()->EraseAnchors(AnchorHandles, UUIDs, OutRequestId);
|
||||
return OculusXRAnchors::GetResultFromXrResult(result);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OculusXRAnchorFunctions.h"
|
||||
|
||||
class OCULUSXRANCHORS_API FOculusXRAnchorFunctionsOpenXR : public IOculusXRAnchorFunctions
|
||||
{
|
||||
public:
|
||||
virtual EOculusXRAnchorResult::Type CreateAnchor(const FTransform& InTransform, uint64& OutRequestId, const FTransform& CameraTransform) override;
|
||||
virtual EOculusXRAnchorResult::Type DestroyAnchor(uint64 AnchorHandle) override;
|
||||
|
||||
virtual EOculusXRAnchorResult::Type TryGetAnchorTransform(uint64 AnchorHandle, FTransform& OutTransform, FOculusXRAnchorLocationFlags& OutLocationFlags, EOculusXRAnchorSpace Space) override;
|
||||
virtual EOculusXRAnchorResult::Type SetAnchorComponentStatus(uint64 AnchorHandle, EOculusXRSpaceComponentType ComponentType, bool Enable, float Timeout, uint64& OutRequestId) override;
|
||||
virtual EOculusXRAnchorResult::Type GetAnchorComponentStatus(uint64 AnchorHandle, EOculusXRSpaceComponentType ComponentType, bool& OutEnabled, bool& OutChangePending) override;
|
||||
virtual EOculusXRAnchorResult::Type GetSupportedAnchorComponents(uint64 AnchorHandle, TArray<EOculusXRSpaceComponentType>& OutSupportedTypes) override;
|
||||
virtual EOculusXRAnchorResult::Type GetAnchorContainerUUIDs(uint64 AnchorHandle, TArray<FOculusXRUUID>& OutUUIDs) override;
|
||||
|
||||
virtual EOculusXRAnchorResult::Type SaveAnchor(uint64 AnchorHandle, EOculusXRSpaceStorageLocation StorageLocation, EOculusXRSpaceStoragePersistenceMode StoragePersistenceMode, uint64& OutRequestId) override;
|
||||
virtual EOculusXRAnchorResult::Type SaveAnchorList(const TArray<uint64>& AnchorHandles, EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId) override;
|
||||
virtual EOculusXRAnchorResult::Type SaveAnchors(const TArray<uint64>& AnchorHandles, uint64& OutRequestId) override;
|
||||
|
||||
virtual EOculusXRAnchorResult::Type DiscoverAnchors(const FOculusXRSpaceDiscoveryInfo& DiscoveryInfo, uint64& OutRequestId) override;
|
||||
virtual EOculusXRAnchorResult::Type QueryAnchors(const FOculusXRSpaceQueryInfo& QueryInfo, uint64& OutRequestId) override;
|
||||
virtual EOculusXRAnchorResult::Type ShareAnchors(const TArray<uint64>& AnchorHandles, const TArray<uint64>& UserIds, uint64& OutRequestId) override;
|
||||
virtual EOculusXRAnchorResult::Type ShareAnchors(const TArray<uint64>& AnchorHandles, const TArray<FOculusXRUUID>& Groups, uint64& OutRequestId) override;
|
||||
|
||||
virtual EOculusXRAnchorResult::Type EraseAnchor(uint64 AnchorHandle, EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId) override;
|
||||
virtual EOculusXRAnchorResult::Type EraseAnchors(const TArray<FOculusXRUInt64>& AnchorHandles, const TArray<FOculusXRUUID>& UUIDs, uint64& OutRequestId) override;
|
||||
};
|
||||
@@ -0,0 +1,846 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRAnchorLatentActions.h"
|
||||
#include "OculusXRAnchorsPrivate.h"
|
||||
#include "OculusXRHMD.h"
|
||||
#include "OculusXRAnchorBPFunctionLibrary.h"
|
||||
#include "OculusXRAnchorDelegates.h"
|
||||
#include "OculusXRAnchorsModule.h"
|
||||
|
||||
//
|
||||
// Create Spatial Anchor
|
||||
//
|
||||
void UOculusXRAsyncAction_CreateSpatialAnchor::Activate()
|
||||
{
|
||||
if (!IsValid(TargetActor))
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid Target Actor passed to CreateSpatialAnchor latent action."));
|
||||
|
||||
Failure.Broadcast(EOculusXRAnchorResult::Failure);
|
||||
return;
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type Result;
|
||||
bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::CreateSpatialAnchor(
|
||||
AnchorTransform,
|
||||
TargetActor,
|
||||
FOculusXRSpatialAnchorCreateDelegate::CreateUObject(this, &UOculusXRAsyncAction_CreateSpatialAnchor::HandleCreateComplete),
|
||||
Result);
|
||||
|
||||
if (!bStartedAsync)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for CreateSpatialAnchor latent action."));
|
||||
Failure.Broadcast(Result);
|
||||
}
|
||||
}
|
||||
|
||||
UOculusXRAsyncAction_CreateSpatialAnchor* UOculusXRAsyncAction_CreateSpatialAnchor::OculusXRAsyncCreateSpatialAnchor(AActor* TargetActor, const FTransform& AnchorTransform)
|
||||
{
|
||||
UOculusXRAsyncAction_CreateSpatialAnchor* Action = NewObject<UOculusXRAsyncAction_CreateSpatialAnchor>();
|
||||
Action->TargetActor = TargetActor;
|
||||
Action->AnchorTransform = AnchorTransform;
|
||||
|
||||
if (IsValid(TargetActor))
|
||||
{
|
||||
Action->RegisterWithGameInstance(TargetActor->GetWorld());
|
||||
}
|
||||
else
|
||||
{
|
||||
Action->RegisterWithGameInstance(GWorld);
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UOculusXRAsyncAction_CreateSpatialAnchor::HandleCreateComplete(EOculusXRAnchorResult::Type CreateResult, UOculusXRAnchorComponent* Anchor)
|
||||
{
|
||||
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(CreateResult))
|
||||
{
|
||||
Success.Broadcast(Anchor, CreateResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
Failure.Broadcast(CreateResult);
|
||||
}
|
||||
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
//
|
||||
// Erase Space
|
||||
//
|
||||
void UOculusXRAsyncAction_EraseAnchor::Activate()
|
||||
{
|
||||
if (!IsValid(TargetActor))
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid Target Actor passed to EraseSpace latent action."));
|
||||
|
||||
Failure.Broadcast(EOculusXRAnchorResult::Failure);
|
||||
return;
|
||||
}
|
||||
|
||||
UOculusXRAnchorComponent* AnchorComponent = TargetActor->FindComponentByClass<UOculusXRAnchorComponent>();
|
||||
if (AnchorComponent == nullptr)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("No anchor on actor in EraseSpace latent action."));
|
||||
|
||||
Failure.Broadcast(EOculusXRAnchorResult::Failure);
|
||||
return;
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type Result;
|
||||
bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::EraseAnchor(
|
||||
AnchorComponent,
|
||||
FOculusXRAnchorEraseDelegate::CreateUObject(this, &UOculusXRAsyncAction_EraseAnchor::HandleEraseAnchorComplete),
|
||||
Result);
|
||||
|
||||
if (!bStartedAsync)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for EraseSpace latent action."));
|
||||
|
||||
Failure.Broadcast(Result);
|
||||
}
|
||||
}
|
||||
|
||||
UOculusXRAsyncAction_EraseAnchor* UOculusXRAsyncAction_EraseAnchor::OculusXRAsyncEraseAnchor(AActor* TargetActor)
|
||||
{
|
||||
UOculusXRAsyncAction_EraseAnchor* Action = NewObject<UOculusXRAsyncAction_EraseAnchor>();
|
||||
Action->TargetActor = TargetActor;
|
||||
|
||||
if (IsValid(TargetActor))
|
||||
{
|
||||
Action->RegisterWithGameInstance(TargetActor->GetWorld());
|
||||
}
|
||||
else
|
||||
{
|
||||
Action->RegisterWithGameInstance(GWorld);
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UOculusXRAsyncAction_EraseAnchor::HandleEraseAnchorComplete(EOculusXRAnchorResult::Type EraseResult, FOculusXRUUID UUID)
|
||||
{
|
||||
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(EraseResult))
|
||||
{
|
||||
Success.Broadcast(TargetActor, UUID, EraseResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
Failure.Broadcast(EraseResult);
|
||||
}
|
||||
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
//
|
||||
// Save Space
|
||||
//
|
||||
void UOculusXRAsyncAction_SaveAnchor::Activate()
|
||||
{
|
||||
if (!IsValid(TargetActor))
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid Target Actor passed to SaveSpace latent action."));
|
||||
|
||||
Failure.Broadcast(EOculusXRAnchorResult::Failure);
|
||||
return;
|
||||
}
|
||||
|
||||
UOculusXRAnchorComponent* AnchorComponent = TargetActor->FindComponentByClass<UOculusXRAnchorComponent>();
|
||||
if (AnchorComponent == nullptr)
|
||||
{
|
||||
Failure.Broadcast(EOculusXRAnchorResult::Failure);
|
||||
return;
|
||||
}
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Log, TEXT("Attempting to save anchor: %s to location %s"), IsValid(AnchorComponent) ? *AnchorComponent->GetName() : TEXT("INVALID ANCHOR"), *UEnum::GetValueAsString(StorageLocation));
|
||||
|
||||
EOculusXRAnchorResult::Type Result;
|
||||
bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::SaveAnchor(
|
||||
AnchorComponent,
|
||||
StorageLocation,
|
||||
FOculusXRAnchorSaveDelegate::CreateUObject(this, &UOculusXRAsyncAction_SaveAnchor::HandleSaveAnchorComplete),
|
||||
Result);
|
||||
|
||||
if (!bStartedAsync)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for SaveSpace latent action."));
|
||||
Failure.Broadcast(Result);
|
||||
}
|
||||
}
|
||||
|
||||
UOculusXRAsyncAction_SaveAnchor* UOculusXRAsyncAction_SaveAnchor::OculusXRAsyncSaveAnchor(AActor* TargetActor, EOculusXRSpaceStorageLocation StorageLocation)
|
||||
{
|
||||
UOculusXRAsyncAction_SaveAnchor* Action = NewObject<UOculusXRAsyncAction_SaveAnchor>();
|
||||
Action->TargetActor = TargetActor;
|
||||
Action->StorageLocation = StorageLocation;
|
||||
|
||||
if (IsValid(TargetActor))
|
||||
{
|
||||
Action->RegisterWithGameInstance(TargetActor->GetWorld());
|
||||
}
|
||||
else
|
||||
{
|
||||
Action->RegisterWithGameInstance(GWorld);
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UOculusXRAsyncAction_SaveAnchor::HandleSaveAnchorComplete(EOculusXRAnchorResult::Type SaveResult, UOculusXRAnchorComponent* Anchor)
|
||||
{
|
||||
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(SaveResult))
|
||||
{
|
||||
Success.Broadcast(Anchor, SaveResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
Failure.Broadcast(SaveResult);
|
||||
}
|
||||
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
//
|
||||
// Save Anchor List
|
||||
//
|
||||
void UOculusXRAsyncAction_SaveAnchorList::Activate()
|
||||
{
|
||||
if (TargetAnchors.Num() == 0)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Empty Target Actor array passed to SaveSpaces latent action."));
|
||||
|
||||
Failure.Broadcast(EOculusXRAnchorResult::Failure);
|
||||
return;
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type Result;
|
||||
bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::SaveAnchorList(
|
||||
TargetAnchors,
|
||||
StorageLocation,
|
||||
FOculusXRAnchorSaveListDelegate::CreateUObject(this, &UOculusXRAsyncAction_SaveAnchorList::HandleSaveAnchorListComplete),
|
||||
Result);
|
||||
|
||||
if (!bStartedAsync)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for SaveSpaceList latent action."));
|
||||
|
||||
Failure.Broadcast(Result);
|
||||
}
|
||||
}
|
||||
|
||||
UOculusXRAsyncAction_SaveAnchorList* UOculusXRAsyncAction_SaveAnchorList::OculusXRAsyncSaveAnchorList(const TArray<AActor*>& TargetActors, EOculusXRSpaceStorageLocation StorageLocation)
|
||||
{
|
||||
UOculusXRAsyncAction_SaveAnchorList* Action = NewObject<UOculusXRAsyncAction_SaveAnchorList>();
|
||||
|
||||
auto ValidActorPtr = TargetActors.FindByPredicate([](AActor* Actor) { return IsValid(Actor); });
|
||||
|
||||
for (auto& it : TargetActors)
|
||||
{
|
||||
if (!IsValid(it))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UOculusXRAnchorComponent* AnchorComponent = it->FindComponentByClass<UOculusXRAnchorComponent>();
|
||||
Action->TargetAnchors.Add(AnchorComponent);
|
||||
}
|
||||
|
||||
Action->StorageLocation = StorageLocation;
|
||||
|
||||
if (ValidActorPtr != nullptr)
|
||||
{
|
||||
Action->RegisterWithGameInstance(*ValidActorPtr);
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UOculusXRAsyncAction_SaveAnchorList::HandleSaveAnchorListComplete(EOculusXRAnchorResult::Type SaveResult, const TArray<UOculusXRAnchorComponent*>& SavedSpaces)
|
||||
{
|
||||
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(SaveResult))
|
||||
{
|
||||
Success.Broadcast(SavedSpaces, SaveResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
Failure.Broadcast(SaveResult);
|
||||
}
|
||||
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
//
|
||||
// Query Spaces
|
||||
//
|
||||
void UOculusXRAsyncAction_QueryAnchors::Activate()
|
||||
{
|
||||
EOculusXRAnchorResult::Type Result;
|
||||
bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::QueryAnchorsAdvanced(
|
||||
QueryInfo,
|
||||
FOculusXRAnchorQueryDelegate::CreateUObject(this, &UOculusXRAsyncAction_QueryAnchors::HandleQueryAnchorsResults),
|
||||
Result);
|
||||
|
||||
if (!bStartedAsync)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for QuerySpaces latent action."));
|
||||
|
||||
Failure.Broadcast(Result);
|
||||
}
|
||||
}
|
||||
|
||||
UOculusXRAsyncAction_QueryAnchors* UOculusXRAsyncAction_QueryAnchors::OculusXRAsyncQueryAnchors(EOculusXRSpaceStorageLocation Location, const TArray<FOculusXRUUID>& UUIDs)
|
||||
{
|
||||
FOculusXRSpaceQueryInfo QueryInfo;
|
||||
QueryInfo.FilterType = EOculusXRSpaceQueryFilterType::FilterByIds;
|
||||
QueryInfo.IDFilter = UUIDs;
|
||||
QueryInfo.Location = Location;
|
||||
QueryInfo.MaxQuerySpaces = UUIDs.Num();
|
||||
|
||||
UOculusXRAsyncAction_QueryAnchors* Action = NewObject<UOculusXRAsyncAction_QueryAnchors>();
|
||||
Action->QueryInfo = QueryInfo;
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
UOculusXRAsyncAction_QueryAnchors* UOculusXRAsyncAction_QueryAnchors::OculusXRAsyncQueryAnchorsAdvanced(const FOculusXRSpaceQueryInfo& QueryInfo)
|
||||
{
|
||||
UOculusXRAsyncAction_QueryAnchors* Action = NewObject<UOculusXRAsyncAction_QueryAnchors>();
|
||||
Action->QueryInfo = QueryInfo;
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UOculusXRAsyncAction_QueryAnchors::HandleQueryAnchorsResults(EOculusXRAnchorResult::Type QueryResult, const TArray<FOculusXRSpaceQueryResult>& Results)
|
||||
{
|
||||
QueryResults = Results;
|
||||
|
||||
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(QueryResult))
|
||||
{
|
||||
Success.Broadcast(QueryResults, QueryResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
Failure.Broadcast(QueryResult);
|
||||
}
|
||||
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
//
|
||||
// Set Component Status with Anchor Actor
|
||||
//
|
||||
void UOculusXRAsyncAction_SetAnchorComponentStatus::Activate()
|
||||
{
|
||||
if (!IsValid(TargetActor))
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid Target Actor passed to SetComponentStatus latent action."));
|
||||
|
||||
Failure.Broadcast(EOculusXRAnchorResult::Failure);
|
||||
return;
|
||||
}
|
||||
|
||||
TargetAnchorComponent = TargetActor->FindComponentByClass<UOculusXRAnchorComponent>();
|
||||
if (TargetAnchorComponent == nullptr)
|
||||
{
|
||||
Failure.Broadcast(EOculusXRAnchorResult::Failure);
|
||||
return;
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type Result;
|
||||
bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::SetAnchorComponentStatus(
|
||||
TargetAnchorComponent,
|
||||
ComponentType,
|
||||
bEnabled,
|
||||
0,
|
||||
FOculusXRAnchorSetComponentStatusDelegate::CreateUObject(this, &UOculusXRAsyncAction_SetAnchorComponentStatus::HandleSetComponentStatusComplete),
|
||||
Result);
|
||||
|
||||
if (!bStartedAsync)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for SetComponentStatus latent action."));
|
||||
|
||||
Failure.Broadcast(Result);
|
||||
}
|
||||
}
|
||||
|
||||
UOculusXRAsyncAction_SetAnchorComponentStatus* UOculusXRAsyncAction_SetAnchorComponentStatus::OculusXRAsyncSetAnchorComponentStatus(AActor* TargetActor, EOculusXRSpaceComponentType ComponentType, bool bEnabled)
|
||||
{
|
||||
UOculusXRAsyncAction_SetAnchorComponentStatus* Action = NewObject<UOculusXRAsyncAction_SetAnchorComponentStatus>();
|
||||
Action->TargetActor = TargetActor;
|
||||
Action->ComponentType = ComponentType;
|
||||
Action->bEnabled = bEnabled;
|
||||
|
||||
if (IsValid(TargetActor))
|
||||
{
|
||||
Action->RegisterWithGameInstance(TargetActor->GetWorld());
|
||||
}
|
||||
else
|
||||
{
|
||||
Action->RegisterWithGameInstance(GWorld);
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UOculusXRAsyncAction_SetAnchorComponentStatus::HandleSetComponentStatusComplete(EOculusXRAnchorResult::Type SetStatusResult, uint64 AnchorHandle, EOculusXRSpaceComponentType SpaceComponentType, bool bResultEnabled)
|
||||
{
|
||||
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(SetStatusResult))
|
||||
{
|
||||
Success.Broadcast(TargetAnchorComponent, SpaceComponentType, bResultEnabled, SetStatusResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
Failure.Broadcast(SetStatusResult);
|
||||
}
|
||||
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
//
|
||||
// Set Component Status
|
||||
//
|
||||
void UOculusXRAsyncAction_SetComponentStatus::Activate()
|
||||
{
|
||||
EOculusXRAnchorResult::Type Result;
|
||||
bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::SetComponentStatus(
|
||||
Component->GetSpace(),
|
||||
Component->GetType(),
|
||||
bEnabled,
|
||||
0,
|
||||
FOculusXRAnchorSetComponentStatusDelegate::CreateUObject(this, &UOculusXRAsyncAction_SetComponentStatus::HandleSetComponentStatusComplete),
|
||||
Result);
|
||||
|
||||
if (!bStartedAsync)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for SetComponentStatus latent action."));
|
||||
|
||||
Failure.Broadcast(Result);
|
||||
}
|
||||
}
|
||||
|
||||
UOculusXRAsyncAction_SetComponentStatus* UOculusXRAsyncAction_SetComponentStatus::OculusXRAsyncSetComponentStatus(UOculusXRBaseAnchorComponent* Component, bool bEnabled)
|
||||
{
|
||||
UOculusXRAsyncAction_SetComponentStatus* Action = NewObject<UOculusXRAsyncAction_SetComponentStatus>();
|
||||
Action->Component = Component;
|
||||
Action->bEnabled = bEnabled;
|
||||
|
||||
Action->RegisterWithGameInstance(GWorld);
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UOculusXRAsyncAction_SetComponentStatus::HandleSetComponentStatusComplete(EOculusXRAnchorResult::Type SetStatusResult, uint64 AnchorHandle, EOculusXRSpaceComponentType SpaceComponentType, bool bResultEnabled)
|
||||
{
|
||||
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(SetStatusResult))
|
||||
{
|
||||
Success.Broadcast(Component, SetStatusResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
Failure.Broadcast(SetStatusResult);
|
||||
}
|
||||
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
//
|
||||
// Share Spaces
|
||||
//
|
||||
void UOculusXRAsyncAction_ShareAnchors::Activate()
|
||||
{
|
||||
if (TargetAnchors.Num() == 0)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Empty Target Actors array passed to ShareSpaces latent action."));
|
||||
|
||||
Failure.Broadcast(EOculusXRAnchorResult::Failure);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ToShareWithIds.Num() == 0)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Empty Target Player IDs array passed to ShareSpaces latent action."));
|
||||
|
||||
Failure.Broadcast(EOculusXRAnchorResult::Failure);
|
||||
return;
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type Result;
|
||||
bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::ShareAnchors(
|
||||
TargetAnchors,
|
||||
ToShareWithIds,
|
||||
FOculusXRAnchorShareDelegate::CreateUObject(this, &UOculusXRAsyncAction_ShareAnchors::HandleShareAnchorsComplete),
|
||||
Result);
|
||||
|
||||
if (!bStartedAsync)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for ShareSpaces latent action."));
|
||||
|
||||
Failure.Broadcast(Result);
|
||||
}
|
||||
}
|
||||
|
||||
UOculusXRAsyncAction_ShareAnchors* UOculusXRAsyncAction_ShareAnchors::OculusXRAsyncShareAnchors(const TArray<AActor*>& TargetActors, const TArray<FString>& ToShareWithIds)
|
||||
{
|
||||
UOculusXRAsyncAction_ShareAnchors* Action = NewObject<UOculusXRAsyncAction_ShareAnchors>();
|
||||
|
||||
for (const auto& UserIDString : ToShareWithIds)
|
||||
{
|
||||
uint64 UserId = FCString::Strtoui64(*UserIDString, nullptr, 10);
|
||||
if (UserId == 0)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("UserID provided to share anchors was invalid or unconvertable: %s"), *UserIDString);
|
||||
}
|
||||
|
||||
Action->ToShareWithIds.Add(UserId);
|
||||
}
|
||||
|
||||
for (auto& it : TargetActors)
|
||||
{
|
||||
if (!IsValid(it))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UOculusXRAnchorComponent* AnchorComponent = it->FindComponentByClass<UOculusXRAnchorComponent>();
|
||||
Action->TargetAnchors.Add(AnchorComponent);
|
||||
}
|
||||
|
||||
auto ValidActorPtr = TargetActors.FindByPredicate([](AActor* Actor) { return IsValid(Actor); });
|
||||
if (ValidActorPtr != nullptr)
|
||||
{
|
||||
Action->RegisterWithGameInstance(*ValidActorPtr);
|
||||
}
|
||||
else
|
||||
{
|
||||
Action->RegisterWithGameInstance(GWorld);
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UOculusXRAsyncAction_ShareAnchors::HandleShareAnchorsComplete(EOculusXRAnchorResult::Type ShareResult, const TArray<UOculusXRAnchorComponent*>& SharedAnchors, const TArray<uint64>& OculusUserIDs)
|
||||
{
|
||||
TArray<FString> OculusUserIDStrings;
|
||||
for (const auto& it : OculusUserIDs)
|
||||
{
|
||||
OculusUserIDStrings.Add(FString::Printf(TEXT("%llu"), it));
|
||||
}
|
||||
|
||||
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(ShareResult))
|
||||
{
|
||||
Success.Broadcast(SharedAnchors, OculusUserIDStrings, ShareResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
Failure.Broadcast(ShareResult);
|
||||
}
|
||||
|
||||
// Unbind and mark for destruction
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
//
|
||||
// Save Anchors
|
||||
//
|
||||
void UOculusXRAsyncAction_SaveAnchors::Activate()
|
||||
{
|
||||
if (TargetAnchors.Num() == 0)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Empty Target Actor array passed to SaveSpaces latent action."));
|
||||
|
||||
Failure.Broadcast(EOculusXRAnchorResult::Failure);
|
||||
return;
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type Result;
|
||||
bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::SaveAnchors(
|
||||
TargetAnchors,
|
||||
FOculusXRSaveAnchorsDelegate::CreateUObject(this, &UOculusXRAsyncAction_SaveAnchors::HandleSaveAnchorsComplete),
|
||||
Result);
|
||||
|
||||
if (!bStartedAsync)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for SaveSpaces latent action."));
|
||||
|
||||
Failure.Broadcast(Result);
|
||||
}
|
||||
}
|
||||
|
||||
UOculusXRAsyncAction_SaveAnchors* UOculusXRAsyncAction_SaveAnchors::OculusXRAsyncSaveAnchors(const TArray<AActor*>& TargetActors)
|
||||
{
|
||||
UOculusXRAsyncAction_SaveAnchors* Action = NewObject<UOculusXRAsyncAction_SaveAnchors>();
|
||||
|
||||
auto ValidActorPtr = TargetActors.FindByPredicate([](AActor* Actor) { return IsValid(Actor); });
|
||||
|
||||
for (auto& it : TargetActors)
|
||||
{
|
||||
if (!IsValid(it))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UOculusXRAnchorComponent* AnchorComponent = it->FindComponentByClass<UOculusXRAnchorComponent>();
|
||||
Action->TargetAnchors.Add(AnchorComponent);
|
||||
}
|
||||
|
||||
if (ValidActorPtr != nullptr)
|
||||
{
|
||||
Action->RegisterWithGameInstance(*ValidActorPtr);
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UOculusXRAsyncAction_SaveAnchors::HandleSaveAnchorsComplete(EOculusXRAnchorResult::Type SaveResult, const TArray<UOculusXRAnchorComponent*>& SavedSpaces)
|
||||
{
|
||||
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(SaveResult))
|
||||
{
|
||||
Success.Broadcast(SavedSpaces, SaveResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
Failure.Broadcast(SaveResult);
|
||||
}
|
||||
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
//
|
||||
// Erase Anchors
|
||||
//
|
||||
void UOculusXRAsyncAction_EraseAnchors::Activate()
|
||||
{
|
||||
if (TargetAnchorHandles.IsEmpty() && TargetUUIDs.IsEmpty())
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Error, TEXT("Empty UUID and Anchor Handles arrays passed to erase anchors. Check that at least one of the anchors, handles, and UUIDs arrays provided have valid elements."));
|
||||
Failure.Broadcast(EOculusXRAnchorResult::Failure);
|
||||
return;
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type Result;
|
||||
bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::EraseAnchors(
|
||||
TargetAnchorHandles,
|
||||
TargetUUIDs,
|
||||
FOculusXREraseAnchorsDelegate::CreateUObject(this, &UOculusXRAsyncAction_EraseAnchors::HandleEraseAnchorsComplete),
|
||||
Result);
|
||||
|
||||
if (!bStartedAsync)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for EraseSpace latent action."));
|
||||
Failure.Broadcast(Result);
|
||||
}
|
||||
}
|
||||
|
||||
UOculusXRAsyncAction_EraseAnchors* UOculusXRAsyncAction_EraseAnchors::OculusXRAsyncEraseAnchors(const TArray<AActor*>& TargetActors, const TArray<FOculusXRUInt64>& AnchorHandles, const TArray<FOculusXRUUID>& AnchorUUIDs)
|
||||
{
|
||||
UOculusXRAsyncAction_EraseAnchors* Action = NewObject<UOculusXRAsyncAction_EraseAnchors>();
|
||||
|
||||
Action->TargetAnchorHandles = AnchorHandles;
|
||||
Action->TargetUUIDs = AnchorUUIDs;
|
||||
|
||||
auto ValidActorPtr = TargetActors.FindByPredicate([](AActor* Actor) { return IsValid(Actor); });
|
||||
|
||||
for (auto& it : TargetActors)
|
||||
{
|
||||
if (!IsValid(it))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UOculusXRAnchorComponent* AnchorComponent = it->FindComponentByClass<UOculusXRAnchorComponent>();
|
||||
if (!IsValid(it))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Action->TargetAnchors.Add(AnchorComponent);
|
||||
|
||||
auto UUID = AnchorComponent->GetUUID();
|
||||
Action->TargetUUIDs.Add(UUID);
|
||||
}
|
||||
|
||||
if (ValidActorPtr != nullptr)
|
||||
{
|
||||
Action->RegisterWithGameInstance(*ValidActorPtr);
|
||||
}
|
||||
else
|
||||
{
|
||||
Action->RegisterWithGameInstance(GWorld);
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UOculusXRAsyncAction_EraseAnchors::HandleEraseAnchorsComplete(EOculusXRAnchorResult::Type EraseResult, const TArray<UOculusXRAnchorComponent*>& ErasedAnchorComponents, const TArray<FOculusXRUInt64>& ErasedAnchorHandles, const TArray<FOculusXRUUID>& ErasedAnchorUUIDs)
|
||||
{
|
||||
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(EraseResult))
|
||||
{
|
||||
Success.Broadcast(TargetAnchors, ErasedAnchorHandles, ErasedAnchorUUIDs, EraseResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
Failure.Broadcast(EraseResult);
|
||||
}
|
||||
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
//
|
||||
// Anchors Discovery
|
||||
//
|
||||
void UOculusXRAsyncAction_DiscoverAnchors::Activate()
|
||||
{
|
||||
EOculusXRAnchorResult::Type Result;
|
||||
bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::DiscoverAnchors(
|
||||
DiscoveryInfo,
|
||||
FOculusXRDiscoverAnchorsResultsDelegate::CreateUObject(this, &UOculusXRAsyncAction_DiscoverAnchors::HandleDiscoverResult),
|
||||
FOculusXRDiscoverAnchorsCompleteDelegate::CreateUObject(this, &UOculusXRAsyncAction_DiscoverAnchors::HandleDiscoverComplete),
|
||||
Result);
|
||||
|
||||
if (!bStartedAsync)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for DiscoverAnchors latent action."));
|
||||
|
||||
Failure.Broadcast(Result);
|
||||
}
|
||||
}
|
||||
|
||||
UOculusXRAsyncAction_DiscoverAnchors* UOculusXRAsyncAction_DiscoverAnchors::OculusXRAsyncDiscoverAnchors(const FOculusXRSpaceDiscoveryInfo& DiscoveryInfo)
|
||||
{
|
||||
UOculusXRAsyncAction_DiscoverAnchors* Action = NewObject<UOculusXRAsyncAction_DiscoverAnchors>();
|
||||
Action->DiscoveryInfo = DiscoveryInfo;
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UOculusXRAsyncAction_DiscoverAnchors::HandleDiscoverResult(const TArray<FOculusXRAnchorsDiscoverResult>& DiscoveredAnchors)
|
||||
{
|
||||
Discovered.Broadcast(DiscoveredAnchors);
|
||||
}
|
||||
|
||||
void UOculusXRAsyncAction_DiscoverAnchors::HandleDiscoverComplete(EOculusXRAnchorResult::Type CompleteResult)
|
||||
{
|
||||
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(CompleteResult))
|
||||
{
|
||||
Complete.Broadcast(CompleteResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
Failure.Broadcast(CompleteResult);
|
||||
}
|
||||
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
//
|
||||
// Get Shared Anchors
|
||||
//
|
||||
void UOculusXRAsyncAction_GetSharedAnchors::Activate()
|
||||
{
|
||||
EOculusXRAnchorResult::Type Result;
|
||||
bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::GetSharedAnchors(
|
||||
Anchors,
|
||||
FOculusXRGetSharedAnchorsDelegate::CreateUObject(this, &UOculusXRAsyncAction_GetSharedAnchors::HandleGetSharedAnchorsResult),
|
||||
Result);
|
||||
|
||||
if (!bStartedAsync)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for DiscoverAnchors latent action."));
|
||||
Failure.Broadcast(Result);
|
||||
}
|
||||
}
|
||||
|
||||
UOculusXRAsyncAction_GetSharedAnchors* UOculusXRAsyncAction_GetSharedAnchors::OculusXRAsyncGetSharedAnchors(const TArray<FOculusXRUUID>& AnchorUUIDs)
|
||||
{
|
||||
UOculusXRAsyncAction_GetSharedAnchors* Action = NewObject<UOculusXRAsyncAction_GetSharedAnchors>();
|
||||
Action->Anchors = AnchorUUIDs;
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UOculusXRAsyncAction_GetSharedAnchors::HandleGetSharedAnchorsResult(EOculusXRAnchorResult::Type Result, const TArray<FOculusXRAnchorsDiscoverResult>& SharedAnchors)
|
||||
{
|
||||
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(Result))
|
||||
{
|
||||
Success.Broadcast(SharedAnchors, Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Failure.Broadcast(Result);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Share with groups
|
||||
//
|
||||
void UOculusXRAsyncAction_ShareAnchorsWithGroups::Activate()
|
||||
{
|
||||
OculusXRAnchors::FOculusXRAnchors::ShareAnchorsAsync(
|
||||
AnchorHandles,
|
||||
GroupUUIDs,
|
||||
OculusXRAnchors::FShareAnchorsWithGroups::FCompleteDelegate::CreateUObject(
|
||||
this,
|
||||
&UOculusXRAsyncAction_ShareAnchorsWithGroups::HandleShareComplete));
|
||||
}
|
||||
|
||||
UOculusXRAsyncAction_ShareAnchorsWithGroups* UOculusXRAsyncAction_ShareAnchorsWithGroups::OculusXRShareAnchorsWithGroupsAsync(const TArray<FOculusXRUUID>& GroupUUIDs, const TArray<FOculusXRUInt64>& AnchorHandles)
|
||||
{
|
||||
UOculusXRAsyncAction_ShareAnchorsWithGroups* Action = NewObject<UOculusXRAsyncAction_ShareAnchorsWithGroups>();
|
||||
Action->GroupUUIDs = GroupUUIDs;
|
||||
Action->AnchorHandles = AnchorHandles;
|
||||
Action->RegisterWithGameInstance(GWorld);
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UOculusXRAsyncAction_ShareAnchorsWithGroups::HandleShareComplete(const OculusXRAnchors::FShareAnchorsWithGroups::FResultType& Result)
|
||||
{
|
||||
if (Result.IsSuccess())
|
||||
{
|
||||
Complete.Broadcast(Result.IsSuccess(), GroupUUIDs, AnchorHandles, Result.GetStatus());
|
||||
}
|
||||
else
|
||||
{
|
||||
Complete.Broadcast(Result.IsSuccess(), TArray<FOculusXRUUID>(), TArray<FOculusXRUInt64>(), Result.GetStatus());
|
||||
}
|
||||
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
//
|
||||
// Get shared anchors from group
|
||||
//
|
||||
void UOculusXRAsyncAction_GetSharedAnchorsFromGroup::Activate()
|
||||
{
|
||||
OculusXRAnchors::FOculusXRAnchors::GetSharedAnchorsAsync(
|
||||
GroupUuid,
|
||||
Anchors,
|
||||
OculusXRAnchors::FGetAnchorsSharedWithGroup::FCompleteDelegate::CreateUObject(
|
||||
this,
|
||||
&UOculusXRAsyncAction_GetSharedAnchorsFromGroup::HandleGetSharedAnchorsComplete));
|
||||
}
|
||||
|
||||
UOculusXRAsyncAction_GetSharedAnchorsFromGroup* UOculusXRAsyncAction_GetSharedAnchorsFromGroup::OculusXRGetSharedAnchorsFromGroupAsync(const FOculusXRUUID& GroupUuid, const TArray<FOculusXRUUID>& AnchorUUIDs)
|
||||
{
|
||||
UOculusXRAsyncAction_GetSharedAnchorsFromGroup* Action = NewObject<UOculusXRAsyncAction_GetSharedAnchorsFromGroup>();
|
||||
Action->GroupUuid = GroupUuid;
|
||||
Action->Anchors = AnchorUUIDs;
|
||||
Action->RegisterWithGameInstance(GWorld);
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UOculusXRAsyncAction_GetSharedAnchorsFromGroup::HandleGetSharedAnchorsComplete(const OculusXRAnchors::FGetAnchorsSharedWithGroup::FResultType& Result)
|
||||
{
|
||||
if (Result.IsSuccess())
|
||||
{
|
||||
Complete.Broadcast(Result.IsSuccess(), Result.GetValue(), Result.GetStatus());
|
||||
}
|
||||
else
|
||||
{
|
||||
Complete.Broadcast(Result.IsSuccess(), OculusXRAnchors::FGetAnchorsSharedWithGroup::FResultValueType(), Result.GetStatus());
|
||||
}
|
||||
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRAnchorManager.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "OculusXRHMD.h"
|
||||
#include "OculusXRAnchorsModule.h"
|
||||
#include "OculusXRAnchorDelegates.h"
|
||||
#include "OculusXRAnchorBPFunctionLibrary.h"
|
||||
#include "OculusXRAnchorTypesPrivate.h"
|
||||
|
||||
#include "OculusXRAnchorFunctionsOVR.h"
|
||||
#include "OculusXRAnchorFunctionsOpenXR.h"
|
||||
|
||||
namespace OculusXRAnchors
|
||||
{
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorManager::CreateAnchor(const FTransform& InTransform, uint64& OutRequestId, const FTransform& CameraTransform)
|
||||
{
|
||||
return GetOculusXRAnchorFunctionsImpl()->CreateAnchor(InTransform, OutRequestId, CameraTransform);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorManager::DestroyAnchor(uint64 AnchorHandle)
|
||||
{
|
||||
return GetOculusXRAnchorFunctionsImpl()->DestroyAnchor(AnchorHandle);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorManager::TryGetAnchorTransform(uint64 AnchorHandle, FTransform& OutTransform, FOculusXRAnchorLocationFlags& OutLocationFlags, EOculusXRAnchorSpace Space)
|
||||
{
|
||||
return GetOculusXRAnchorFunctionsImpl()->TryGetAnchorTransform(AnchorHandle, OutTransform, OutLocationFlags, Space);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorManager::SetAnchorComponentStatus(uint64 AnchorHandle, EOculusXRSpaceComponentType ComponentType, bool Enable, float Timeout, uint64& OutRequestId)
|
||||
{
|
||||
return GetOculusXRAnchorFunctionsImpl()->SetAnchorComponentStatus(AnchorHandle, ComponentType, Enable, Timeout, OutRequestId);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorManager::GetAnchorComponentStatus(uint64 AnchorHandle, EOculusXRSpaceComponentType ComponentType, bool& OutEnabled, bool& OutChangePending)
|
||||
{
|
||||
return GetOculusXRAnchorFunctionsImpl()->GetAnchorComponentStatus(AnchorHandle, ComponentType, OutEnabled, OutChangePending);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorManager::GetSupportedAnchorComponents(uint64 AnchorHandle, TArray<EOculusXRSpaceComponentType>& OutSupportedTypes)
|
||||
{
|
||||
return GetOculusXRAnchorFunctionsImpl()->GetSupportedAnchorComponents(AnchorHandle, OutSupportedTypes);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorManager::GetAnchorContainerUUIDs(uint64 AnchorHandle, TArray<FOculusXRUUID>& OutUUIDs)
|
||||
{
|
||||
return GetOculusXRAnchorFunctionsImpl()->GetAnchorContainerUUIDs(AnchorHandle, OutUUIDs);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorManager::SaveAnchor(uint64 AnchorHandle, EOculusXRSpaceStorageLocation StorageLocation, EOculusXRSpaceStoragePersistenceMode StoragePersistenceMode, uint64& OutRequestId)
|
||||
{
|
||||
return GetOculusXRAnchorFunctionsImpl()->SaveAnchor(AnchorHandle, StorageLocation, StoragePersistenceMode, OutRequestId);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorManager::SaveAnchorList(const TArray<uint64>& AnchorHandles, EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId)
|
||||
{
|
||||
return GetOculusXRAnchorFunctionsImpl()->SaveAnchorList(AnchorHandles, StorageLocation, OutRequestId);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorManager::SaveAnchors(const TArray<uint64>& AnchorHandles, uint64& OutRequestId)
|
||||
{
|
||||
return GetOculusXRAnchorFunctionsImpl()->SaveAnchors(AnchorHandles, OutRequestId);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorManager::DiscoverAnchors(const FOculusXRSpaceDiscoveryInfo& DiscoveryInfo, uint64& OutRequestId)
|
||||
{
|
||||
return GetOculusXRAnchorFunctionsImpl()->DiscoverAnchors(DiscoveryInfo, OutRequestId);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorManager::QueryAnchors(const FOculusXRSpaceQueryInfo& QueryInfo, uint64& OutRequestId)
|
||||
{
|
||||
return GetOculusXRAnchorFunctionsImpl()->QueryAnchors(QueryInfo, OutRequestId);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorManager::ShareAnchors(const TArray<uint64>& AnchorHandles, const TArray<uint64>& UserIds, uint64& OutRequestId)
|
||||
{
|
||||
return GetOculusXRAnchorFunctionsImpl()->ShareAnchors(AnchorHandles, UserIds, OutRequestId);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorManager::ShareAnchors(const TArray<uint64>& AnchorHandles, FOculusXRUUID GroupId, uint64& OutRequestId)
|
||||
{
|
||||
return GetOculusXRAnchorFunctionsImpl()->ShareAnchors(AnchorHandles, { GroupId }, OutRequestId);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorManager::ShareAnchors(const TArray<uint64>& AnchorHandles, const TArray<FOculusXRUUID>& Groups, uint64& OutRequestId)
|
||||
{
|
||||
return GetOculusXRAnchorFunctionsImpl()->ShareAnchors(AnchorHandles, Groups, OutRequestId);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorManager::EraseAnchor(uint64 AnchorHandle, EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId)
|
||||
{
|
||||
return GetOculusXRAnchorFunctionsImpl()->EraseAnchor(AnchorHandle, StorageLocation, OutRequestId);
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type FOculusXRAnchorManager::EraseAnchors(const TArray<FOculusXRUInt64>& AnchorHandles, const TArray<FOculusXRUUID>& UUIDs, uint64& OutRequestId)
|
||||
{
|
||||
return GetOculusXRAnchorFunctionsImpl()->EraseAnchors(AnchorHandles, UUIDs, OutRequestId);
|
||||
}
|
||||
|
||||
TSharedPtr<IOculusXRAnchorFunctions> FOculusXRAnchorManager::AnchorFunctionsImpl = nullptr;
|
||||
TSharedPtr<IOculusXRAnchorFunctions> FOculusXRAnchorManager::GetOculusXRAnchorFunctionsImpl()
|
||||
{
|
||||
if (AnchorFunctionsImpl == nullptr)
|
||||
{
|
||||
const FName SystemName(TEXT("OpenXR"));
|
||||
const bool IsOpenXR = GEngine->XRSystem.IsValid() && (GEngine->XRSystem->GetSystemName() == SystemName);
|
||||
if (OculusXRHMD::FOculusXRHMD::GetOculusXRHMD() != nullptr)
|
||||
{
|
||||
AnchorFunctionsImpl = MakeShared<FOculusXRAnchorFunctionsOVR>();
|
||||
}
|
||||
else if (IsOpenXR)
|
||||
{
|
||||
AnchorFunctionsImpl = MakeShared<FOculusXRAnchorFunctionsOpenXR>();
|
||||
}
|
||||
}
|
||||
|
||||
check(AnchorFunctionsImpl);
|
||||
return AnchorFunctionsImpl;
|
||||
}
|
||||
|
||||
} // namespace OculusXRAnchors
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "OculusXRAnchorComponent.h"
|
||||
#include "OculusXRHMDPrivate.h"
|
||||
#include "OVR_Plugin_Types.h"
|
||||
#include "OculusXRAnchorFunctions.h"
|
||||
|
||||
namespace OculusXRAnchors
|
||||
{
|
||||
struct OCULUSXRANCHORS_API FOculusXRAnchorManager
|
||||
{
|
||||
public:
|
||||
static EOculusXRAnchorResult::Type CreateAnchor(const FTransform& InTransform, uint64& OutRequestId, const FTransform& CameraTransform);
|
||||
static EOculusXRAnchorResult::Type DestroyAnchor(uint64 AnchorHandle);
|
||||
|
||||
static EOculusXRAnchorResult::Type TryGetAnchorTransform(uint64 AnchorHandle, FTransform& OutTransform, FOculusXRAnchorLocationFlags& OutLocationFlags, EOculusXRAnchorSpace Space);
|
||||
static EOculusXRAnchorResult::Type SetAnchorComponentStatus(uint64 AnchorHandle, EOculusXRSpaceComponentType ComponentType, bool Enable, float Timeout, uint64& OutRequestId);
|
||||
static EOculusXRAnchorResult::Type GetAnchorComponentStatus(uint64 AnchorHandle, EOculusXRSpaceComponentType ComponentType, bool& OutEnabled, bool& OutChangePending);
|
||||
static EOculusXRAnchorResult::Type GetSupportedAnchorComponents(uint64 AnchorHandle, TArray<EOculusXRSpaceComponentType>& OutSupportedTypes);
|
||||
static EOculusXRAnchorResult::Type GetAnchorContainerUUIDs(uint64 AnchorHandle, TArray<FOculusXRUUID>& OutUUIDs);
|
||||
|
||||
static EOculusXRAnchorResult::Type SaveAnchor(uint64 AnchorHandle, EOculusXRSpaceStorageLocation StorageLocation, EOculusXRSpaceStoragePersistenceMode StoragePersistenceMode, uint64& OutRequestId);
|
||||
static EOculusXRAnchorResult::Type SaveAnchorList(const TArray<uint64>& AnchorHandles, EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId);
|
||||
static EOculusXRAnchorResult::Type SaveAnchors(const TArray<uint64>& AnchorHandles, uint64& OutRequestId);
|
||||
|
||||
static EOculusXRAnchorResult::Type DiscoverAnchors(const FOculusXRSpaceDiscoveryInfo& DiscoveryInfo, uint64& OutRequestId);
|
||||
static EOculusXRAnchorResult::Type QueryAnchors(const FOculusXRSpaceQueryInfo& QueryInfo, uint64& OutRequestId);
|
||||
static EOculusXRAnchorResult::Type ShareAnchors(const TArray<uint64>& AnchorHandles, const TArray<uint64>& UserIds, uint64& OutRequestId);
|
||||
static EOculusXRAnchorResult::Type ShareAnchors(const TArray<uint64>& AnchorHandles, FOculusXRUUID GroupId, uint64& OutRequestId);
|
||||
static EOculusXRAnchorResult::Type ShareAnchors(const TArray<uint64>& AnchorHandles, const TArray<FOculusXRUUID>& Groups, uint64& OutRequestId);
|
||||
|
||||
static EOculusXRAnchorResult::Type EraseAnchor(uint64 AnchorHandle, EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId);
|
||||
static EOculusXRAnchorResult::Type EraseAnchors(const TArray<FOculusXRUInt64>& AnchorHandles, const TArray<FOculusXRUUID>& UUIDs, uint64& OutRequestId);
|
||||
|
||||
private:
|
||||
static TSharedPtr<IOculusXRAnchorFunctions> GetOculusXRAnchorFunctionsImpl();
|
||||
static TSharedPtr<IOculusXRAnchorFunctions> AnchorFunctionsImpl;
|
||||
};
|
||||
} // namespace OculusXRAnchors
|
||||
@@ -0,0 +1,151 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRAnchorsRequests.h"
|
||||
#include "OculusXRAnchorsUtil.h"
|
||||
#include "OculusXRHMDModule.h"
|
||||
#include "OculusXRAnchorsModule.h"
|
||||
#include "OculusXRAnchorManager.h"
|
||||
#include "OculusXRAnchorDelegates.h"
|
||||
|
||||
namespace OculusXRAnchors
|
||||
{
|
||||
OculusXR::FAsyncRequestBase::RequestId DetermineRequestId(EOculusXRAnchorResult::Type Result, uint64 Id)
|
||||
{
|
||||
return OVRP_SUCCESS(Result) ? OculusXR::FAsyncRequestBase::RequestId(Id) : OculusXR::FAsyncRequestBase::RequestId(OculusXR::INVALID_TASK_REQUEST_ID);
|
||||
}
|
||||
|
||||
FShareAnchorsWithGroups::FShareAnchorsWithGroups(const TArray<FOculusXRUUID>& TargetGroups, const TArray<FOculusXRUInt64>& AnchorsToShare)
|
||||
: Groups(TargetGroups)
|
||||
, Anchors(AnchorsToShare)
|
||||
{
|
||||
CallbackHandle = FOculusXRAnchorEventDelegates::OculusShareAnchorsComplete.AddStatic(&FShareAnchorsWithGroups::OnShareComplete);
|
||||
}
|
||||
|
||||
FShareAnchorsWithGroups::~FShareAnchorsWithGroups()
|
||||
{
|
||||
FOculusXRAnchorEventDelegates::OculusShareAnchorsComplete.Remove(CallbackHandle);
|
||||
}
|
||||
|
||||
void FShareAnchorsWithGroups::OnInitRequest()
|
||||
{
|
||||
TArray<uint64> anchorHandles;
|
||||
Algo::Transform(Anchors, anchorHandles, [](const FOculusXRUInt64& In) { return In.GetValue(); });
|
||||
|
||||
uint64 requestId;
|
||||
auto result = FOculusXRAnchorManager::ShareAnchors(anchorHandles, Groups, requestId);
|
||||
|
||||
SetRequestId(DetermineRequestId(result, requestId));
|
||||
SetInitialResult(result);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Log, TEXT("Started FShareAnchorsWithGroups: RequestId: %llu -- EventId: %llu -- Result: %s"),
|
||||
GetRequestId().Id, GetEventId().Id, *GetStringFromResult(result));
|
||||
}
|
||||
|
||||
void FShareAnchorsWithGroups::OnShareComplete(FOculusXRUInt64 RequestId, EOculusXRAnchorResult::Type Result)
|
||||
{
|
||||
auto taskPtr = OculusXR::FAsyncRequestSystem::GetRequest<FShareAnchorsWithGroups>(
|
||||
OculusXR::FAsyncRequestBase::RequestId{ RequestId });
|
||||
|
||||
if (taskPtr.IsValid())
|
||||
{
|
||||
OculusXR::FAsyncRequestSystem::CompleteRequest<FShareAnchorsWithGroups>(
|
||||
taskPtr->GetEventId(),
|
||||
FShareAnchorsWithGroups::FResultType::FromResult(
|
||||
Result,
|
||||
FShareAnchorsWithGroups::FResultValueType(taskPtr->GetGroups(), taskPtr->GetAnchors())));
|
||||
}
|
||||
}
|
||||
|
||||
FGetAnchorsSharedWithGroup::FGetAnchorsSharedWithGroup(const FOculusXRUUID& TargetGroup, const TArray<FOculusXRUUID>& WantedAnchors)
|
||||
: Group(TargetGroup)
|
||||
, RequestedAnchors(WantedAnchors)
|
||||
{
|
||||
CallbackHandleComplete = FOculusXRAnchorEventDelegates::OculusSpaceQueryComplete.AddStatic(&FGetAnchorsSharedWithGroup::OnQueryComplete);
|
||||
CallbackHandleResults = FOculusXRAnchorEventDelegates::OculusSpaceQueryResult.AddStatic(&FGetAnchorsSharedWithGroup::OnQueryResultAvailable);
|
||||
}
|
||||
|
||||
FGetAnchorsSharedWithGroup::~FGetAnchorsSharedWithGroup()
|
||||
{
|
||||
FOculusXRAnchorEventDelegates::OculusSpaceQueryComplete.Remove(CallbackHandleComplete);
|
||||
FOculusXRAnchorEventDelegates::OculusSpaceQueryResult.Remove(CallbackHandleResults);
|
||||
}
|
||||
|
||||
void FGetAnchorsSharedWithGroup::OnResultsAvailable(const TArray<FOculusXRAnchor>& Results)
|
||||
{
|
||||
RetrievedAnchors += Results;
|
||||
}
|
||||
|
||||
void FGetAnchorsSharedWithGroup::OnInitRequest()
|
||||
{
|
||||
constexpr int32 maxSpaces = 1024;
|
||||
constexpr double timeout = 0;
|
||||
|
||||
FOculusXRSpaceQueryInfo queryInfo;
|
||||
queryInfo.FilterType = EOculusXRSpaceQueryFilterType::FilterByGroup;
|
||||
queryInfo.GroupUUIDFilter = Group;
|
||||
queryInfo.Location = EOculusXRSpaceStorageLocation::Cloud;
|
||||
queryInfo.MaxQuerySpaces = maxSpaces;
|
||||
queryInfo.Timeout = timeout;
|
||||
queryInfo.IDFilter = RequestedAnchors;
|
||||
|
||||
uint64 requestId;
|
||||
auto result = FOculusXRAnchorManager::QueryAnchors(queryInfo, requestId);
|
||||
|
||||
SetRequestId(DetermineRequestId(result, requestId));
|
||||
SetInitialResult(result);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Log, TEXT("Started FGetAnchorsSharedWithGroup: RequestId: %llu -- EventId: %llu -- Result: %s"),
|
||||
GetRequestId().Id, GetEventId().Id, *GetStringFromResult(result));
|
||||
}
|
||||
|
||||
void FGetAnchorsSharedWithGroup::OnQueryComplete(FOculusXRUInt64 RequestId, EOculusXRAnchorResult::Type Result)
|
||||
{
|
||||
auto taskPtr = OculusXR::FAsyncRequestSystem::GetRequest<FGetAnchorsSharedWithGroup>(
|
||||
OculusXR::FAsyncRequestBase::RequestId{ RequestId.GetValue() });
|
||||
|
||||
// If there is a valid get shared anchors request we can complete and exit without firing legacy event delegates
|
||||
if (taskPtr.IsValid())
|
||||
{
|
||||
OculusXR::FAsyncRequestSystem::CompleteRequest<FGetAnchorsSharedWithGroup>(
|
||||
taskPtr->GetEventId(),
|
||||
FGetAnchorsSharedWithGroup::FResultType::FromResult(Result, taskPtr->GetRetrievedAnchors()));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void FGetAnchorsSharedWithGroup::OnQueryResultAvailable(FOculusXRUInt64 RequestId, FOculusXRUInt64 AnchorHandle, FOculusXRUUID AnchorUuid)
|
||||
{
|
||||
auto taskPtr = OculusXR::FAsyncRequestSystem::GetRequest<FGetAnchorsSharedWithGroup>(
|
||||
OculusXR::FAsyncRequestBase::RequestId{ RequestId.GetValue() });
|
||||
|
||||
if (taskPtr.IsValid())
|
||||
{
|
||||
TArray<EOculusXRSpaceComponentType> supportedTypes;
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT(" Found Element: Space: %llu -- UUID: %s"), AnchorHandle.Value, *AnchorUuid.ToString());
|
||||
|
||||
uint64 tempOut;
|
||||
FOculusXRAnchorManager::GetSupportedAnchorComponents(AnchorHandle, supportedTypes);
|
||||
|
||||
if (supportedTypes.Contains(EOculusXRSpaceComponentType::Locatable))
|
||||
{
|
||||
FOculusXRAnchorManager::SetAnchorComponentStatus(AnchorHandle, EOculusXRSpaceComponentType::Locatable, true, 0.0f, tempOut);
|
||||
}
|
||||
|
||||
if (supportedTypes.Contains(EOculusXRSpaceComponentType::Sharable))
|
||||
{
|
||||
FOculusXRAnchorManager::SetAnchorComponentStatus(AnchorHandle, EOculusXRSpaceComponentType::Sharable, true, 0.0f, tempOut);
|
||||
}
|
||||
|
||||
if (supportedTypes.Contains(EOculusXRSpaceComponentType::Storable))
|
||||
{
|
||||
FOculusXRAnchorManager::SetAnchorComponentStatus(AnchorHandle, EOculusXRSpaceComponentType::Storable, true, 0.0f, tempOut);
|
||||
}
|
||||
|
||||
taskPtr->OnResultsAvailable({ FOculusXRAnchor(AnchorHandle, AnchorUuid) });
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace OculusXRAnchors
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRAnchorTypes.h"
|
||||
#include "OculusXRHMDPrivate.h"
|
||||
#include "OculusXRAnchorsModule.h"
|
||||
#include "OculusXRAnchorTypesPrivate.h"
|
||||
|
||||
bool FOculusXRUInt64::operator==(const FOculusXRUInt64& Right) const
|
||||
{
|
||||
return IsEqual(Right);
|
||||
}
|
||||
bool FOculusXRUInt64::operator!=(const FOculusXRUInt64& Right) const
|
||||
{
|
||||
return !IsEqual(Right);
|
||||
}
|
||||
|
||||
FOculusXRUUID::FOculusXRUUID()
|
||||
{
|
||||
FMemory::Memzero(&UUIDBytes, OCULUSXR_UUID_SIZE);
|
||||
}
|
||||
|
||||
FOculusXRUUID::FOculusXRUUID(const UuidArray& In)
|
||||
{
|
||||
FMemory::Memcpy(UUIDBytes, In);
|
||||
}
|
||||
|
||||
bool FOculusXRUUID::operator==(const FOculusXRUUID& Right) const
|
||||
{
|
||||
return IsEqual(Right);
|
||||
}
|
||||
|
||||
bool FOculusXRUUID::operator!=(const FOculusXRUUID& Right) const
|
||||
{
|
||||
return !IsEqual(Right);
|
||||
}
|
||||
|
||||
bool FOculusXRUUID::IsValidUUID() const
|
||||
{
|
||||
static uint8 InvalidUUID[OCULUSXR_UUID_SIZE] = { 0 };
|
||||
|
||||
return FMemory::Memcmp(UUIDBytes, InvalidUUID, OCULUSXR_UUID_SIZE) != 0;
|
||||
}
|
||||
|
||||
bool FOculusXRUUID::IsEqual(const FOculusXRUUID& Other) const
|
||||
{
|
||||
return FMemory::Memcmp(UUIDBytes, Other.UUIDBytes, OCULUSXR_UUID_SIZE) == 0;
|
||||
}
|
||||
|
||||
bool FOculusXRUUID::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess)
|
||||
{
|
||||
uint8 data[16] = { 0 };
|
||||
|
||||
for (uint8 i = 0; i < OCULUSXR_UUID_SIZE; ++i)
|
||||
{
|
||||
data[i] = UUIDBytes[i];
|
||||
};
|
||||
|
||||
for (uint8 i = 0; i < OCULUSXR_UUID_SIZE; ++i)
|
||||
{
|
||||
Ar << data[i];
|
||||
};
|
||||
|
||||
for (uint8 i = 0; i < OCULUSXR_UUID_SIZE; ++i)
|
||||
{
|
||||
UUIDBytes[i] = data[i];
|
||||
};
|
||||
|
||||
bOutSuccess = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FArchive& operator<<(FArchive& Ar, FOculusXRUUID& UUID)
|
||||
{
|
||||
bool bOutSuccess = false;
|
||||
UUID.NetSerialize(Ar, nullptr, bOutSuccess);
|
||||
|
||||
return Ar;
|
||||
}
|
||||
|
||||
bool FOculusXRUUID::Serialize(FArchive& Ar)
|
||||
{
|
||||
Ar << *this;
|
||||
return true;
|
||||
}
|
||||
|
||||
FString FOculusXRUUID::ToString() const
|
||||
{
|
||||
return BytesToHex(UUIDBytes, OCULUSXR_UUID_SIZE);
|
||||
}
|
||||
|
||||
void ovrpSpaceDiscoveryFilterIdsDelete::operator()(ovrpSpaceDiscoveryFilterIds* ptr) const
|
||||
{
|
||||
if (ptr != nullptr)
|
||||
{
|
||||
delete ptr;
|
||||
ptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const ovrpSpaceDiscoveryFilterHeader* UOculusXRSpaceDiscoveryIdsFilter::GenerateOVRPFilter()
|
||||
{
|
||||
uint32 IdsCount = (uint32)Uuids.Num();
|
||||
|
||||
wrappedUUIDs.SetNumZeroed(IdsCount);
|
||||
OVRPFilterIds.reset(new ovrpSpaceDiscoveryFilterIds{ ovrpSpaceDiscoveryFilterType_Ids, IdsCount });
|
||||
UE_LOG(LogOculusXRAnchors, Display, TEXT("UUID discovery filter:"));
|
||||
|
||||
for (uint32 i = 0; i < IdsCount; ++i)
|
||||
{
|
||||
FMemory::Memcpy(wrappedUUIDs[i].data, Uuids[i].UUIDBytes);
|
||||
UE_LOG(LogOculusXRAnchors, Display, TEXT("\t%s"), *Uuids[i].ToString());
|
||||
}
|
||||
|
||||
OVRPFilterIds->Uuids = reinterpret_cast<ovrpUuid*>(wrappedUUIDs.GetData());
|
||||
|
||||
return (const ovrpSpaceDiscoveryFilterHeader*)OVRPFilterIds.get();
|
||||
}
|
||||
|
||||
void ovrpSpaceDiscoveryFilterComponentsDelete::operator()(ovrpSpaceDiscoveryFilterComponents* ptr) const
|
||||
{
|
||||
if (ptr != nullptr)
|
||||
{
|
||||
delete ptr;
|
||||
ptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const ovrpSpaceDiscoveryFilterHeader* UOculusXRSpaceDiscoveryComponentsFilter::GenerateOVRPFilter()
|
||||
{
|
||||
OVRPFilterComponent.reset(new ovrpSpaceDiscoveryFilterComponents{ ovrpSpaceDiscoveryFilterType_Components, ConvertToOvrpComponentType(ComponentType) });
|
||||
UE_LOG(LogOculusXRAnchors, Display, TEXT("Component discovery filter %d"), ComponentType);
|
||||
return (const ovrpSpaceDiscoveryFilterHeader*)OVRPFilterComponent.get();
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRAnchorTypesPrivate.h"
|
||||
|
||||
ovrpSpaceComponentType ConvertToOvrpComponentType(const EOculusXRSpaceComponentType ComponentType)
|
||||
{
|
||||
ovrpSpaceComponentType ovrpType = ovrpSpaceComponentType_Max;
|
||||
switch (ComponentType)
|
||||
{
|
||||
case EOculusXRSpaceComponentType::Locatable:
|
||||
ovrpType = ovrpSpaceComponentType_Locatable;
|
||||
break;
|
||||
case EOculusXRSpaceComponentType::Sharable:
|
||||
ovrpType = ovrpSpaceComponentType_Sharable;
|
||||
break;
|
||||
case EOculusXRSpaceComponentType::Storable:
|
||||
ovrpType = ovrpSpaceComponentType_Storable;
|
||||
break;
|
||||
case EOculusXRSpaceComponentType::ScenePlane:
|
||||
ovrpType = ovrpSpaceComponentType_Bounded2D;
|
||||
break;
|
||||
case EOculusXRSpaceComponentType::SceneVolume:
|
||||
ovrpType = ovrpSpaceComponentType_Bounded3D;
|
||||
break;
|
||||
case EOculusXRSpaceComponentType::SemanticClassification:
|
||||
ovrpType = ovrpSpaceComponentType_SemanticLabels;
|
||||
break;
|
||||
case EOculusXRSpaceComponentType::RoomLayout:
|
||||
ovrpType = ovrpSpaceComponentType_RoomLayout;
|
||||
break;
|
||||
case EOculusXRSpaceComponentType::SpaceContainer:
|
||||
ovrpType = ovrpSpaceComponentType_SpaceContainer;
|
||||
break;
|
||||
case EOculusXRSpaceComponentType::TriangleMesh:
|
||||
ovrpType = ovrpSpaceComponentType_TriangleMesh;
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
|
||||
return ovrpType;
|
||||
}
|
||||
|
||||
EOculusXRSpaceComponentType ConvertToUEComponentType(const ovrpSpaceComponentType ComponentType)
|
||||
{
|
||||
EOculusXRSpaceComponentType ueComponentType = EOculusXRSpaceComponentType::Undefined;
|
||||
switch (ComponentType)
|
||||
{
|
||||
case ovrpSpaceComponentType_Locatable:
|
||||
ueComponentType = EOculusXRSpaceComponentType::Locatable;
|
||||
break;
|
||||
case ovrpSpaceComponentType_Sharable:
|
||||
ueComponentType = EOculusXRSpaceComponentType::Sharable;
|
||||
break;
|
||||
case ovrpSpaceComponentType_Storable:
|
||||
ueComponentType = EOculusXRSpaceComponentType::Storable;
|
||||
break;
|
||||
case ovrpSpaceComponentType_Bounded2D:
|
||||
ueComponentType = EOculusXRSpaceComponentType::ScenePlane;
|
||||
break;
|
||||
case ovrpSpaceComponentType_Bounded3D:
|
||||
ueComponentType = EOculusXRSpaceComponentType::SceneVolume;
|
||||
break;
|
||||
case ovrpSpaceComponentType_SemanticLabels:
|
||||
ueComponentType = EOculusXRSpaceComponentType::SemanticClassification;
|
||||
break;
|
||||
case ovrpSpaceComponentType_RoomLayout:
|
||||
ueComponentType = EOculusXRSpaceComponentType::RoomLayout;
|
||||
break;
|
||||
case ovrpSpaceComponentType_SpaceContainer:
|
||||
ueComponentType = EOculusXRSpaceComponentType::SpaceContainer;
|
||||
break;
|
||||
case ovrpSpaceComponentType_TriangleMesh:
|
||||
ueComponentType = EOculusXRSpaceComponentType::TriangleMesh;
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
|
||||
return ueComponentType;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
#include "OculusXRAnchorTypes.h"
|
||||
#include "OVR_Plugin_Types.h"
|
||||
|
||||
ovrpSpaceComponentType ConvertToOvrpComponentType(const EOculusXRSpaceComponentType ComponentType);
|
||||
EOculusXRSpaceComponentType ConvertToUEComponentType(const ovrpSpaceComponentType ComponentType);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,355 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRAnchorsEventPolling.h"
|
||||
#include "OculusXRHMD.h"
|
||||
#include "OculusXRAnchorsModule.h"
|
||||
#include "OculusXRAnchorManager.h"
|
||||
#include "OculusXRAnchorDelegates.h"
|
||||
#include "OculusXRAnchorBPFunctionLibrary.h"
|
||||
#include "OculusXRAnchorTypesPrivate.h"
|
||||
#include "OculusXRAnchorsUtil.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include <vector>
|
||||
|
||||
namespace OculusXRAnchors
|
||||
{
|
||||
template <typename T>
|
||||
void GetEventDataAs(ovrpEventDataBuffer& Buffer, T& OutEventData)
|
||||
{
|
||||
memcpy(&OutEventData, reinterpret_cast<uint8*>(&Buffer), sizeof(T));
|
||||
}
|
||||
|
||||
// For template specializations that use the legacy event padding
|
||||
constexpr size_t LegacyOffset = 8;
|
||||
template <typename T>
|
||||
void GetLegacyEventDataAs(ovrpEventDataBuffer& Buffer, T& OutEventData)
|
||||
{
|
||||
OutEventData.EventType = Buffer.EventType;
|
||||
memcpy(((uint8*)&OutEventData) + LegacyOffset, Buffer.EventData, sizeof(T) - LegacyOffset);
|
||||
}
|
||||
|
||||
#define DECLARE_EVENT_DATA_CAST_SPECIALIZATION(T) \
|
||||
template <> \
|
||||
void GetEventDataAs(ovrpEventDataBuffer& Buffer, T& OutEventData) \
|
||||
{ \
|
||||
GetLegacyEventDataAs<T>(Buffer, OutEventData); \
|
||||
}
|
||||
|
||||
DECLARE_EVENT_DATA_CAST_SPECIALIZATION(ovrpEventDataSpatialAnchorCreateComplete);
|
||||
DECLARE_EVENT_DATA_CAST_SPECIALIZATION(ovrpEventDataSpaceSetStatusComplete);
|
||||
DECLARE_EVENT_DATA_CAST_SPECIALIZATION(ovrpEventSpaceQueryResults);
|
||||
DECLARE_EVENT_DATA_CAST_SPECIALIZATION(ovrpEventSpaceQueryComplete);
|
||||
DECLARE_EVENT_DATA_CAST_SPECIALIZATION(ovrpEventSpaceStorageSaveResult);
|
||||
DECLARE_EVENT_DATA_CAST_SPECIALIZATION(ovrpEventSpaceListSaveResult);
|
||||
DECLARE_EVENT_DATA_CAST_SPECIALIZATION(ovrpEventSpaceStorageEraseResult);
|
||||
DECLARE_EVENT_DATA_CAST_SPECIALIZATION(ovrpEventSpaceShareResult);
|
||||
DECLARE_EVENT_DATA_CAST_SPECIALIZATION(ovrpEventDataSpaceDiscoveryComplete);
|
||||
DECLARE_EVENT_DATA_CAST_SPECIALIZATION(ovrpEventSpaceDiscoveryResults);
|
||||
DECLARE_EVENT_DATA_CAST_SPECIALIZATION(ovrpEventSpacesSaveResult);
|
||||
DECLARE_EVENT_DATA_CAST_SPECIALIZATION(ovrpEventSpacesEraseResult);
|
||||
|
||||
void FOculusXRAnchorsEventPolling::OnPollEvent(ovrpEventDataBuffer* EventDataBuffer, bool& EventPollResult)
|
||||
{
|
||||
ovrpEventDataBuffer& buf = *EventDataBuffer;
|
||||
EventPollResult = true;
|
||||
|
||||
switch (buf.EventType)
|
||||
{
|
||||
case ovrpEventType_SpatialAnchorCreateComplete:
|
||||
{
|
||||
ovrpEventDataSpatialAnchorCreateComplete AnchorCreateEvent;
|
||||
GetEventDataAs(buf, AnchorCreateEvent);
|
||||
|
||||
const FOculusXRUInt64 RequestId(AnchorCreateEvent.requestId);
|
||||
const FOculusXRUInt64 Space(AnchorCreateEvent.space);
|
||||
const FOculusXRUUID BPUUID(AnchorCreateEvent.uuid.data);
|
||||
|
||||
FOculusXRAnchorEventDelegates::OculusSpatialAnchorCreateComplete.Broadcast(
|
||||
RequestId,
|
||||
GetResultFromOVRResult(AnchorCreateEvent.result),
|
||||
Space,
|
||||
BPUUID);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpatialAnchorCreateComplete Request ID: %llu -- Space: %llu -- UUID: %s -- Result: %d"),
|
||||
RequestId.GetValue(),
|
||||
Space.GetValue(),
|
||||
*BPUUID.ToString(),
|
||||
AnchorCreateEvent.result);
|
||||
|
||||
break;
|
||||
}
|
||||
case ovrpEventType_SpaceSetComponentStatusComplete:
|
||||
{
|
||||
ovrpEventDataSpaceSetStatusComplete SetStatusEvent;
|
||||
GetEventDataAs(buf, SetStatusEvent);
|
||||
|
||||
// translate to BP types
|
||||
const FOculusXRUInt64 RequestId(SetStatusEvent.requestId);
|
||||
const FOculusXRUInt64 Space(SetStatusEvent.space);
|
||||
EOculusXRSpaceComponentType BPSpaceComponentType = ConvertToUEComponentType(SetStatusEvent.componentType);
|
||||
const FOculusXRUUID BPUUID(SetStatusEvent.uuid.data);
|
||||
const bool bEnabled = (SetStatusEvent.enabled == ovrpBool_True);
|
||||
|
||||
FOculusXRAnchorEventDelegates::OculusSpaceSetComponentStatusComplete.Broadcast(
|
||||
RequestId,
|
||||
GetResultFromOVRResult(SetStatusEvent.result),
|
||||
Space,
|
||||
BPUUID,
|
||||
BPSpaceComponentType,
|
||||
bEnabled);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpaceSetComponentStatusComplete Request ID: %llu -- Type: %d -- Enabled: %d -- Space: %llu -- Result: %d"),
|
||||
SetStatusEvent.requestId,
|
||||
SetStatusEvent.componentType,
|
||||
SetStatusEvent.enabled,
|
||||
SetStatusEvent.space,
|
||||
SetStatusEvent.result);
|
||||
|
||||
break;
|
||||
}
|
||||
case ovrpEventType_SpaceQueryResults:
|
||||
{
|
||||
ovrpEventSpaceQueryResults QueryEvent;
|
||||
GetEventDataAs(buf, QueryEvent);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpaceQueryResults Request ID: %llu"), QueryEvent.requestId);
|
||||
|
||||
ovrpUInt32 ovrpOutCapacity = 0;
|
||||
auto getCapacityResult = FOculusXRHMDModule::GetPluginWrapper().RetrieveSpaceQueryResults(&QueryEvent.requestId, 0, &ovrpOutCapacity, nullptr);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Space Query Results: Capacity Request -- Request ID: %llu -- Capacity: %d -- Result: %s"),
|
||||
QueryEvent.requestId,
|
||||
ovrpOutCapacity,
|
||||
*GetStringFromResult(GetResultFromOVRResult(getCapacityResult)));
|
||||
|
||||
std::vector<ovrpSpaceQueryResult> spaceQueryResults(ovrpOutCapacity);
|
||||
auto getQueryResult = FOculusXRHMDModule::GetPluginWrapper().RetrieveSpaceQueryResults(&QueryEvent.requestId, spaceQueryResults.size(), &ovrpOutCapacity, spaceQueryResults.data());
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Space Query Results: Retrieved Elements -- Request ID: %llu -- Result: %s"),
|
||||
QueryEvent.requestId,
|
||||
*GetStringFromResult(GetResultFromOVRResult(getQueryResult)));
|
||||
|
||||
if (OVRP_FAILURE(getCapacityResult) || OVRP_FAILURE(getQueryResult))
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to retrieve anchor data from space query results -- Request ID: %llu"), QueryEvent.requestId);
|
||||
return;
|
||||
}
|
||||
|
||||
FOculusXRUInt64 RequestId(QueryEvent.requestId);
|
||||
FOculusXRAnchorEventDelegates::OculusSpaceQueryResults.Broadcast(RequestId);
|
||||
|
||||
for (const auto& queryResultElement : spaceQueryResults)
|
||||
{
|
||||
FOculusXRUInt64 anchorHandle(queryResultElement.space);
|
||||
FOculusXRUUID uuid(queryResultElement.uuid.data);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpaceQueryResult -- Space: %llu -- UUID: %s"), anchorHandle.Value, *uuid.ToString());
|
||||
|
||||
FOculusXRAnchorEventDelegates::OculusSpaceQueryResult.Broadcast(RequestId, anchorHandle, uuid);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ovrpEventType_SpaceQueryComplete:
|
||||
{
|
||||
ovrpEventSpaceQueryComplete QueryCompleteEvent;
|
||||
GetEventDataAs(buf, QueryCompleteEvent);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpaceQueryComplete Request ID: %llu -- Result: %d"), QueryCompleteEvent.requestId, QueryCompleteEvent.result);
|
||||
|
||||
// translate to BP types
|
||||
const FOculusXRUInt64 RequestId(QueryCompleteEvent.requestId);
|
||||
const bool bSucceeded = QueryCompleteEvent.result >= 0;
|
||||
|
||||
FOculusXRAnchorEventDelegates::OculusSpaceQueryComplete.Broadcast(
|
||||
RequestId,
|
||||
GetResultFromOVRResult(QueryCompleteEvent.result));
|
||||
|
||||
break;
|
||||
}
|
||||
case ovrpEventType_SpaceSaveComplete:
|
||||
{
|
||||
ovrpEventSpaceStorageSaveResult StorageResult;
|
||||
GetEventDataAs(buf, StorageResult);
|
||||
|
||||
// translate to BP types
|
||||
const FOculusXRUUID uuid(StorageResult.uuid.data);
|
||||
const FOculusXRUInt64 FSpace(StorageResult.space);
|
||||
const FOculusXRUInt64 FRequest(StorageResult.requestId);
|
||||
const bool bResult = StorageResult.result >= 0;
|
||||
|
||||
FOculusXRAnchorEventDelegates::OculusSpaceSaveComplete.Broadcast(
|
||||
FRequest,
|
||||
FSpace,
|
||||
bResult,
|
||||
GetResultFromOVRResult(StorageResult.result),
|
||||
uuid);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpaceSaveComplete Request ID: %llu -- Space: %llu -- Result: %d"), StorageResult.requestId, StorageResult.space, StorageResult.result);
|
||||
|
||||
break;
|
||||
}
|
||||
case ovrpEventType_SpaceListSaveResult:
|
||||
{
|
||||
ovrpEventSpaceListSaveResult SpaceListSaveResult;
|
||||
GetEventDataAs(buf, SpaceListSaveResult);
|
||||
|
||||
FOculusXRUInt64 RequestId(SpaceListSaveResult.requestId);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpaceListSaveResult Request ID: %llu -- Result: %d"), SpaceListSaveResult.requestId, SpaceListSaveResult.result);
|
||||
FOculusXRAnchorEventDelegates::OculusSpaceListSaveComplete.Broadcast(
|
||||
RequestId,
|
||||
GetResultFromOVRResult(SpaceListSaveResult.result));
|
||||
|
||||
break;
|
||||
}
|
||||
case ovrpEventType_SpaceEraseComplete:
|
||||
{
|
||||
ovrpEventSpaceStorageEraseResult SpaceEraseEvent;
|
||||
GetEventDataAs(buf, SpaceEraseEvent);
|
||||
|
||||
// translate to BP types
|
||||
const FOculusXRUUID uuid(SpaceEraseEvent.uuid.data);
|
||||
const FOculusXRUInt64 FRequestId(SpaceEraseEvent.requestId);
|
||||
const EOculusXRSpaceStorageLocation BPLocation = (SpaceEraseEvent.location == ovrpSpaceStorageLocation_Local) ? EOculusXRSpaceStorageLocation::Local : EOculusXRSpaceStorageLocation::Invalid;
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpaceEraseComplete Request ID: %llu -- Result: %d -- UUID: %s"),
|
||||
SpaceEraseEvent.requestId,
|
||||
SpaceEraseEvent.result,
|
||||
*UOculusXRAnchorBPFunctionLibrary::AnchorUUIDToString(SpaceEraseEvent.uuid.data));
|
||||
|
||||
FOculusXRAnchorEventDelegates::OculusSpaceEraseComplete.Broadcast(
|
||||
FRequestId,
|
||||
GetResultFromOVRResult(SpaceEraseEvent.result),
|
||||
uuid,
|
||||
BPLocation);
|
||||
break;
|
||||
}
|
||||
case ovrpEventType_SpaceShareResult:
|
||||
{
|
||||
ovrpEventSpaceShareResult SpaceShareSpaceResult;
|
||||
GetEventDataAs(buf, SpaceShareSpaceResult);
|
||||
|
||||
FOculusXRUInt64 RequestId(SpaceShareSpaceResult.requestId);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpaceShareSpaceResult Request ID: %llu -- Result: %d"),
|
||||
SpaceShareSpaceResult.requestId,
|
||||
SpaceShareSpaceResult.result);
|
||||
|
||||
FOculusXRAnchorEventDelegates::OculusSpaceShareComplete.Broadcast(
|
||||
RequestId,
|
||||
GetResultFromOVRResult(SpaceShareSpaceResult.result));
|
||||
|
||||
break;
|
||||
}
|
||||
case ovrpEventType_SpaceDiscoveryComplete:
|
||||
{
|
||||
ovrpEventDataSpaceDiscoveryComplete SpaceDiscoveryCompleteEvent;
|
||||
GetEventDataAs(buf, SpaceDiscoveryCompleteEvent);
|
||||
|
||||
FOculusXRUInt64 RequestId(SpaceDiscoveryCompleteEvent.requestId);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpaceDiscoveryComplete Request ID: %llu -- Result: %d"),
|
||||
RequestId.GetValue(),
|
||||
SpaceDiscoveryCompleteEvent.result);
|
||||
|
||||
FOculusXRAnchorEventDelegates::OculusAnchorsDiscoverComplete.Broadcast(
|
||||
RequestId,
|
||||
GetResultFromOVRResult(SpaceDiscoveryCompleteEvent.result));
|
||||
|
||||
break;
|
||||
}
|
||||
case ovrpEventType_SpaceDiscoveryResultsAvailable:
|
||||
{
|
||||
ovrpEventSpaceDiscoveryResults SpaceDiscoveryResultsEvent;
|
||||
GetEventDataAs(buf, SpaceDiscoveryResultsEvent);
|
||||
|
||||
FOculusXRUInt64 RequestId(SpaceDiscoveryResultsEvent.requestId);
|
||||
|
||||
ovrpSpaceDiscoveryResults OVRPResults = { 0, 0, nullptr };
|
||||
|
||||
// get capacity
|
||||
bool GetCapacityResult = FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().RetrieveSpaceDiscoveryResults(RequestId, &OVRPResults));
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Log, TEXT("ovrpEventType_SpaceDiscoveryResultsAvailable Request ID: %llu -- Capacity: %d -- Result: %d"),
|
||||
uint64(RequestId), OVRPResults.ResultCountOutput, GetCapacityResult);
|
||||
|
||||
// get data
|
||||
OVRPResults.ResultCapacityInput = OVRPResults.ResultCountOutput;
|
||||
std::vector<ovrpSpaceDiscoveryResult> ResultsData(OVRPResults.ResultCountOutput);
|
||||
OVRPResults.Results = ResultsData.data();
|
||||
bool GetDiscoveryResult = FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().RetrieveSpaceDiscoveryResults(RequestId, &OVRPResults));
|
||||
TArray<FOculusXRAnchorsDiscoverResult> SpaceDiscoveryResults;
|
||||
|
||||
for (auto& Element : ResultsData)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpaceDiscoveryResultsAvailable Space: %llu -- Result: %d"),
|
||||
Element.Space,
|
||||
GetDiscoveryResult);
|
||||
|
||||
SpaceDiscoveryResults.Add(FOculusXRAnchorsDiscoverResult(Element.Space, Element.Uuid.data));
|
||||
}
|
||||
|
||||
FOculusXRAnchorEventDelegates::OculusAnchorsDiscoverResults.Broadcast(RequestId, SpaceDiscoveryResults);
|
||||
|
||||
break;
|
||||
}
|
||||
case ovrpEventType_SpacesSaveResult:
|
||||
{
|
||||
ovrpEventSpacesSaveResult SpacesSaveEvent;
|
||||
GetEventDataAs(buf, SpacesSaveEvent);
|
||||
|
||||
FOculusXRUInt64 RequestId(SpacesSaveEvent.requestId);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpacesSaveResult Request ID: %llu -- Result: %d"),
|
||||
RequestId.GetValue(),
|
||||
SpacesSaveEvent.result);
|
||||
|
||||
FOculusXRAnchorEventDelegates::OculusAnchorsSaveComplete.Broadcast(
|
||||
RequestId,
|
||||
GetResultFromOVRResult(SpacesSaveEvent.result));
|
||||
|
||||
break;
|
||||
}
|
||||
case ovrpEventType_SpacesEraseResult:
|
||||
{
|
||||
ovrpEventSpacesEraseResult SpacesEraseEvent;
|
||||
GetEventDataAs(buf, SpacesEraseEvent);
|
||||
|
||||
FOculusXRUInt64 RequestId(SpacesEraseEvent.requestId);
|
||||
FOculusXRUInt64 Result(SpacesEraseEvent.result);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpacesEraseResult Request ID: %llu -- Result: %d"),
|
||||
RequestId.GetValue(),
|
||||
SpacesEraseEvent.result);
|
||||
|
||||
FOculusXRAnchorEventDelegates::OculusAnchorsEraseComplete.Broadcast(
|
||||
RequestId,
|
||||
GetResultFromOVRResult(SpacesEraseEvent.result));
|
||||
|
||||
break;
|
||||
}
|
||||
case ovrpEventType_ShareSpacesComplete:
|
||||
{
|
||||
ovrpEventShareSpacesComplete EventData;
|
||||
GetEventDataAs(buf, EventData);
|
||||
|
||||
UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_ShareSpacesComplete Request ID: %llu -- Result: %s"),
|
||||
EventData.RequestId,
|
||||
*GetStringFromResult(GetResultFromOVRResult(EventData.Result)));
|
||||
|
||||
FOculusXRAnchorEventDelegates::OculusShareAnchorsComplete.Broadcast(
|
||||
EventData.RequestId,
|
||||
GetResultFromOVRResult(EventData.Result));
|
||||
|
||||
break;
|
||||
}
|
||||
case ovrpEventType_None:
|
||||
default:
|
||||
{
|
||||
EventPollResult = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace OculusXRAnchors
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OculusXRAnchorTypes.h"
|
||||
#include "OculusXRPluginWrapper.h"
|
||||
|
||||
namespace OculusXRAnchors
|
||||
{
|
||||
struct FOculusXRAnchorsEventPolling
|
||||
{
|
||||
public:
|
||||
static void OnPollEvent(ovrpEventDataBuffer* EventDataBuffer, bool& EventPollResult);
|
||||
};
|
||||
|
||||
} // namespace OculusXRAnchors
|
||||
@@ -0,0 +1,109 @@
|
||||
// @lint-ignore-every LICENSELINT
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "OculusXRAnchorsModule.h"
|
||||
|
||||
#if OCULUS_ANCHORS_SUPPORTED_PLATFORMS
|
||||
#include "OculusXRHMDModule.h"
|
||||
#include "OculusXRHMD.h"
|
||||
#include "OculusXRAnchors.h"
|
||||
#include "OculusXRAnchorsEventPolling.h"
|
||||
#include "OculusXRAnchorComponents.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOculusXRAnchors);
|
||||
|
||||
#define LOCTEXT_NAMESPACE "OculusXRAnchors"
|
||||
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
// FOculusXRAnchorsModule
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
void FOculusXRAnchorsModule::StartupModule()
|
||||
{
|
||||
AnchorsXR = MakeShareable(new XRAnchors::FAnchorsXR());
|
||||
AnchorsXR->RegisterAsOpenXRExtension();
|
||||
|
||||
AddCreateAnchorComponentInterface(this);
|
||||
Anchors.Initialize();
|
||||
|
||||
FCoreDelegates::OnPostEngineInit.AddRaw(this, &FOculusXRAnchorsModule::OnPostEngineInit);
|
||||
}
|
||||
|
||||
void FOculusXRAnchorsModule::ShutdownModule()
|
||||
{
|
||||
Anchors.Teardown();
|
||||
}
|
||||
|
||||
void FOculusXRAnchorsModule::OnPostEngineInit()
|
||||
{
|
||||
if (IsRunningCommandlet())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GEngine)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OculusXRHMD::FOculusXRHMD* HMD = OculusXRHMD::FOculusXRHMD::GetOculusXRHMD();
|
||||
if (!HMD)
|
||||
{
|
||||
UE_LOG(LogOculusXRAnchors, Warning, TEXT("Unable to retrieve OculusXRHMD, cannot add event polling delegates."));
|
||||
return;
|
||||
}
|
||||
|
||||
HMD->AddEventPollingDelegate(OculusXRHMD::FOculusXRHMDEventPollingDelegate::CreateStatic(&OculusXRAnchors::FOculusXRAnchorsEventPolling::OnPollEvent));
|
||||
}
|
||||
|
||||
void FOculusXRAnchorsModule::AddCreateAnchorComponentInterface(IOculusXRCreateAnchorComponent* CastInterface)
|
||||
{
|
||||
CreateComponentInterfaces.AddUnique(CastInterface);
|
||||
}
|
||||
|
||||
void FOculusXRAnchorsModule::RemoveCreateAnchorComponentInterface(IOculusXRCreateAnchorComponent* CastInterface)
|
||||
{
|
||||
CreateComponentInterfaces.Remove(CastInterface);
|
||||
}
|
||||
|
||||
UOculusXRBaseAnchorComponent* FOculusXRAnchorsModule::CreateAnchorComponent(uint64 AnchorHandle, EOculusXRSpaceComponentType Type, UObject* Outer)
|
||||
{
|
||||
for (auto& it : CreateComponentInterfaces)
|
||||
{
|
||||
auto comp = it->TryCreateAnchorComponent(AnchorHandle, Type, Outer);
|
||||
if (IsValid(comp))
|
||||
{
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UOculusXRBaseAnchorComponent* FOculusXRAnchorsModule::TryCreateAnchorComponent(uint64 AnchorHandle, EOculusXRSpaceComponentType Type, UObject* Outer)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case EOculusXRSpaceComponentType::Locatable:
|
||||
return UOculusXRBaseAnchorComponent::FromSpace<UOculusXRLocatableAnchorComponent>(AnchorHandle, Outer);
|
||||
case EOculusXRSpaceComponentType::SpaceContainer:
|
||||
return UOculusXRBaseAnchorComponent::FromSpace<UOculusXRSpaceContainerAnchorComponent>(AnchorHandle, Outer);
|
||||
case EOculusXRSpaceComponentType::Sharable:
|
||||
return UOculusXRBaseAnchorComponent::FromSpace<UOculusXRSharableAnchorComponent>(AnchorHandle, Outer);
|
||||
case EOculusXRSpaceComponentType::Storable:
|
||||
return UOculusXRBaseAnchorComponent::FromSpace<UOculusXRStorableAnchorComponent>(AnchorHandle, Outer);
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
OculusXRAnchors::FOculusXRAnchors* FOculusXRAnchorsModule::GetOculusAnchors()
|
||||
{
|
||||
FOculusXRAnchorsModule& Module = FModuleManager::LoadModuleChecked<FOculusXRAnchorsModule>(TEXT("OculusXRAnchors"));
|
||||
return &Module.Anchors;
|
||||
}
|
||||
|
||||
#endif // OCULUS_ANCHORS_SUPPORTED_PLATFORMS
|
||||
|
||||
IMPLEMENT_MODULE(FOculusXRAnchorsModule, OculusXRAnchors)
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,63 @@
|
||||
// @lint-ignore-every LICENSELINT
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
#include "IOculusXRAnchorsModule.h"
|
||||
#include "OculusXRAnchors.h"
|
||||
#include "openxr/OculusXRAnchorsXR.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "OculusAnchors"
|
||||
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
// FOculusXRAnchorsModule
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
|
||||
#if OCULUS_ANCHORS_SUPPORTED_PLATFORMS
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogOculusXRAnchors, Log, All);
|
||||
|
||||
typedef TSharedPtr<XRAnchors::FAnchorsXR, ESPMode::ThreadSafe> FAnchorsXRPtr;
|
||||
|
||||
class FOculusXRAnchorsModule : public IOculusXRAnchorsModule, IOculusXRCreateAnchorComponent
|
||||
{
|
||||
public:
|
||||
static inline FOculusXRAnchorsModule& Get()
|
||||
{
|
||||
return FModuleManager::LoadModuleChecked<FOculusXRAnchorsModule>("OculusXRAnchors");
|
||||
}
|
||||
|
||||
virtual ~FOculusXRAnchorsModule() = default;
|
||||
|
||||
// IModuleInterface interface
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
void OnPostEngineInit();
|
||||
|
||||
// IOculusXRAnchorsModule
|
||||
virtual void AddCreateAnchorComponentInterface(IOculusXRCreateAnchorComponent* CastInterface) override;
|
||||
virtual void RemoveCreateAnchorComponentInterface(IOculusXRCreateAnchorComponent* CastInterface) override;
|
||||
virtual UOculusXRBaseAnchorComponent* CreateAnchorComponent(uint64 AnchorHandle, EOculusXRSpaceComponentType Type, UObject* Outer) override;
|
||||
|
||||
// IOculusXRAnchorComponentCaster
|
||||
virtual UOculusXRBaseAnchorComponent* TryCreateAnchorComponent(uint64 AnchorHandle, EOculusXRSpaceComponentType Type, UObject* Outer) override;
|
||||
|
||||
static OculusXRAnchors::FOculusXRAnchors* GetOculusAnchors();
|
||||
|
||||
FAnchorsXRPtr GetXrAnchors() { return AnchorsXR; }
|
||||
|
||||
private:
|
||||
TArray<IOculusXRCreateAnchorComponent*> CreateComponentInterfaces;
|
||||
OculusXRAnchors::FOculusXRAnchors Anchors;
|
||||
FAnchorsXRPtr AnchorsXR;
|
||||
};
|
||||
|
||||
#else // OCULUS_ANCHORS_SUPPORTED_PLATFORMS
|
||||
|
||||
class FOculusXRAnchorsModule : public FDefaultModuleImpl
|
||||
{
|
||||
};
|
||||
|
||||
#endif // OCULUS_ANCHORS_SUPPORTED_PLATFORMS
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OculusXRAnchorsModule.h"
|
||||
@@ -0,0 +1,240 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRAnchorsUtil.h"
|
||||
#include <khronos/openxr/openxr.h>
|
||||
|
||||
namespace OculusXRAnchors
|
||||
{
|
||||
EOculusXRAnchorResult::Type GetResultFromOVRResult(ovrpResult OVRResult)
|
||||
{
|
||||
switch (OVRResult)
|
||||
{
|
||||
case ovrpSuccess:
|
||||
return EOculusXRAnchorResult::Success;
|
||||
case ovrpWarning_BoundaryVisibilitySuppressionNotAllowed:
|
||||
return EOculusXRAnchorResult::Warning_BoundaryVisibilitySuppressionNotAllowed;
|
||||
|
||||
case ovrpFailure:
|
||||
return EOculusXRAnchorResult::Failure;
|
||||
case ovrpFailure_InvalidParameter:
|
||||
return EOculusXRAnchorResult::Failure_InvalidParameter;
|
||||
case ovrpFailure_NotInitialized:
|
||||
return EOculusXRAnchorResult::Failure_NotInitialized;
|
||||
case ovrpFailure_InvalidOperation:
|
||||
return EOculusXRAnchorResult::Failure_InvalidOperation;
|
||||
case ovrpFailure_Unsupported:
|
||||
return EOculusXRAnchorResult::Failure_Unsupported;
|
||||
case ovrpFailure_NotYetImplemented:
|
||||
return EOculusXRAnchorResult::Failure_NotYetImplemented;
|
||||
case ovrpFailure_OperationFailed:
|
||||
return EOculusXRAnchorResult::Failure_OperationFailed;
|
||||
case ovrpFailure_InsufficientSize:
|
||||
return EOculusXRAnchorResult::Failure_InsufficientSize;
|
||||
case ovrpFailure_DataIsInvalid:
|
||||
return EOculusXRAnchorResult::Failure_DataIsInvalid;
|
||||
case ovrpFailure_DeprecatedOperation:
|
||||
return EOculusXRAnchorResult::Failure_DeprecatedOperation;
|
||||
case ovrpFailure_ErrorLimitReached:
|
||||
return EOculusXRAnchorResult::Failure_ErrorLimitReached;
|
||||
case ovrpFailure_ErrorInitializationFailed:
|
||||
return EOculusXRAnchorResult::Failure_ErrorInitializationFailed;
|
||||
|
||||
// Query Spaces
|
||||
case ovrpFailure_SpaceCloudStorageDisabled:
|
||||
return EOculusXRAnchorResult::Failure_SpaceCloudStorageDisabled;
|
||||
case ovrpFailure_SpaceMappingInsufficient:
|
||||
return EOculusXRAnchorResult::Failure_SpaceMappingInsufficient;
|
||||
case ovrpFailure_SpaceLocalizationFailed:
|
||||
return EOculusXRAnchorResult::Failure_SpaceLocalizationFailed;
|
||||
case ovrpFailure_SpaceNetworkTimeout:
|
||||
return EOculusXRAnchorResult::Failure_SpaceNetworkTimeout;
|
||||
case ovrpFailure_SpaceNetworkRequestFailed:
|
||||
return EOculusXRAnchorResult::Failure_SpaceNetworkRequestFailed;
|
||||
|
||||
// APD
|
||||
case ovrpFailure_SpaceInsufficientResources:
|
||||
return EOculusXRAnchorResult::Failure_SpaceInsufficientResources;
|
||||
case ovrpFailure_SpaceStorageAtCapacity:
|
||||
return EOculusXRAnchorResult::Failure_SpaceStorageAtCapacity;
|
||||
case ovrpFailure_SpaceInsufficientView:
|
||||
return EOculusXRAnchorResult::Failure_SpaceInsufficientView;
|
||||
case ovrpFailure_SpacePermissionInsufficient:
|
||||
return EOculusXRAnchorResult::Failure_SpacePermissionInsufficient;
|
||||
case ovrpFailure_SpaceRateLimited:
|
||||
return EOculusXRAnchorResult::Failure_SpaceRateLimited;
|
||||
case ovrpFailure_SpaceTooDark:
|
||||
return EOculusXRAnchorResult::Failure_SpaceTooDark;
|
||||
case ovrpFailure_SpaceTooBright:
|
||||
return EOculusXRAnchorResult::Failure_SpaceTooBright;
|
||||
|
||||
default:
|
||||
return OVRP_SUCCESS(OVRResult) ? EOculusXRAnchorResult::Success : EOculusXRAnchorResult::Failure;
|
||||
}
|
||||
}
|
||||
|
||||
EOculusXRAnchorResult::Type GetResultFromXrResult(XrResult Result)
|
||||
{
|
||||
switch (Result)
|
||||
{
|
||||
case XR_SUCCESS:
|
||||
return EOculusXRAnchorResult::Success;
|
||||
// case XR_BOUNDARY_VISIBILITY_SUPPRESSION_NOT_ALLOWED_META:
|
||||
// return EOculusXRAnchorResult::Warning_BoundaryVisibilitySuppressionNotAllowed;
|
||||
|
||||
case XR_ERROR_VALIDATION_FAILURE:
|
||||
return EOculusXRAnchorResult::Failure_InvalidParameter;
|
||||
case XR_ERROR_RUNTIME_FAILURE:
|
||||
return EOculusXRAnchorResult::Failure_OperationFailed;
|
||||
case XR_ERROR_FEATURE_UNSUPPORTED:
|
||||
return EOculusXRAnchorResult::Failure_Unsupported;
|
||||
case XR_ERROR_FUNCTION_UNSUPPORTED:
|
||||
return EOculusXRAnchorResult::Failure_NotYetImplemented;
|
||||
case XR_ERROR_SIZE_INSUFFICIENT:
|
||||
return EOculusXRAnchorResult::Failure_InsufficientSize;
|
||||
case XR_ERROR_LIMIT_REACHED:
|
||||
return EOculusXRAnchorResult::Failure_ErrorLimitReached;
|
||||
case XR_ERROR_INITIALIZATION_FAILED:
|
||||
return EOculusXRAnchorResult::Failure_ErrorInitializationFailed;
|
||||
|
||||
// Query Spaces
|
||||
case XR_ERROR_SPACE_CLOUD_STORAGE_DISABLED_FB:
|
||||
return EOculusXRAnchorResult::Failure_SpaceCloudStorageDisabled;
|
||||
case XR_ERROR_SPACE_MAPPING_INSUFFICIENT_FB:
|
||||
return EOculusXRAnchorResult::Failure_SpaceMappingInsufficient;
|
||||
case XR_ERROR_SPACE_LOCALIZATION_FAILED_FB:
|
||||
return EOculusXRAnchorResult::Failure_SpaceLocalizationFailed;
|
||||
case XR_ERROR_SPACE_NETWORK_TIMEOUT_FB:
|
||||
return EOculusXRAnchorResult::Failure_SpaceNetworkTimeout;
|
||||
case XR_ERROR_SPACE_NETWORK_REQUEST_FAILED_FB:
|
||||
return EOculusXRAnchorResult::Failure_SpaceNetworkRequestFailed;
|
||||
|
||||
// APD
|
||||
case XR_ERROR_SPACE_INSUFFICIENT_RESOURCES_META:
|
||||
return EOculusXRAnchorResult::Failure_SpaceInsufficientResources;
|
||||
case XR_ERROR_SPACE_STORAGE_AT_CAPACITY_META:
|
||||
return EOculusXRAnchorResult::Failure_SpaceStorageAtCapacity;
|
||||
case XR_ERROR_SPACE_INSUFFICIENT_VIEW_META:
|
||||
return EOculusXRAnchorResult::Failure_SpaceInsufficientView;
|
||||
case XR_ERROR_SPACE_PERMISSION_INSUFFICIENT_META:
|
||||
return EOculusXRAnchorResult::Failure_SpacePermissionInsufficient;
|
||||
case XR_ERROR_SPACE_RATE_LIMITED_META:
|
||||
return EOculusXRAnchorResult::Failure_SpaceRateLimited;
|
||||
case XR_ERROR_SPACE_TOO_DARK_META:
|
||||
return EOculusXRAnchorResult::Failure_SpaceTooDark;
|
||||
case XR_ERROR_SPACE_TOO_BRIGHT_META:
|
||||
return EOculusXRAnchorResult::Failure_SpaceTooBright;
|
||||
|
||||
default:
|
||||
return XR_SUCCEEDED(Result) ? EOculusXRAnchorResult::Success : EOculusXRAnchorResult::Failure;
|
||||
}
|
||||
}
|
||||
|
||||
EOculusXRSpaceComponentType ToComponentType(XrSpaceComponentTypeFB XrType)
|
||||
{
|
||||
switch (XrType)
|
||||
{
|
||||
case XR_SPACE_COMPONENT_TYPE_LOCATABLE_FB:
|
||||
return EOculusXRSpaceComponentType::Locatable;
|
||||
case XR_SPACE_COMPONENT_TYPE_STORABLE_FB:
|
||||
return EOculusXRSpaceComponentType::Storable;
|
||||
case XR_SPACE_COMPONENT_TYPE_SHARABLE_FB:
|
||||
return EOculusXRSpaceComponentType::Sharable;
|
||||
case XR_SPACE_COMPONENT_TYPE_BOUNDED_2D_FB:
|
||||
return EOculusXRSpaceComponentType::ScenePlane;
|
||||
case XR_SPACE_COMPONENT_TYPE_BOUNDED_3D_FB:
|
||||
return EOculusXRSpaceComponentType::SceneVolume;
|
||||
case XR_SPACE_COMPONENT_TYPE_SEMANTIC_LABELS_FB:
|
||||
return EOculusXRSpaceComponentType::SemanticClassification;
|
||||
case XR_SPACE_COMPONENT_TYPE_ROOM_LAYOUT_FB:
|
||||
return EOculusXRSpaceComponentType::RoomLayout;
|
||||
case XR_SPACE_COMPONENT_TYPE_SPACE_CONTAINER_FB:
|
||||
return EOculusXRSpaceComponentType::SpaceContainer;
|
||||
case XR_SPACE_COMPONENT_TYPE_TRIANGLE_MESH_META:
|
||||
return EOculusXRSpaceComponentType::TriangleMesh;
|
||||
default:
|
||||
return EOculusXRSpaceComponentType::Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
XrSpaceComponentTypeFB ToComponentType(EOculusXRSpaceComponentType ComponentType)
|
||||
{
|
||||
switch (ComponentType)
|
||||
{
|
||||
case EOculusXRSpaceComponentType::Locatable:
|
||||
return XR_SPACE_COMPONENT_TYPE_LOCATABLE_FB;
|
||||
case EOculusXRSpaceComponentType::Storable:
|
||||
return XR_SPACE_COMPONENT_TYPE_STORABLE_FB;
|
||||
case EOculusXRSpaceComponentType::Sharable:
|
||||
return XR_SPACE_COMPONENT_TYPE_SHARABLE_FB;
|
||||
case EOculusXRSpaceComponentType::ScenePlane:
|
||||
return XR_SPACE_COMPONENT_TYPE_BOUNDED_2D_FB;
|
||||
case EOculusXRSpaceComponentType::SceneVolume:
|
||||
return XR_SPACE_COMPONENT_TYPE_BOUNDED_3D_FB;
|
||||
case EOculusXRSpaceComponentType::SemanticClassification:
|
||||
return XR_SPACE_COMPONENT_TYPE_SEMANTIC_LABELS_FB;
|
||||
case EOculusXRSpaceComponentType::RoomLayout:
|
||||
return XR_SPACE_COMPONENT_TYPE_ROOM_LAYOUT_FB;
|
||||
case EOculusXRSpaceComponentType::SpaceContainer:
|
||||
return XR_SPACE_COMPONENT_TYPE_SPACE_CONTAINER_FB;
|
||||
case EOculusXRSpaceComponentType::TriangleMesh:
|
||||
return XR_SPACE_COMPONENT_TYPE_TRIANGLE_MESH_META;
|
||||
default:
|
||||
return XR_SPACE_COMPONENT_TYPE_MAX_ENUM_FB;
|
||||
}
|
||||
}
|
||||
|
||||
EOculusXRSpaceStorageLocation ToStorageLocation(XrSpaceStorageLocationFB XrStorageLocation)
|
||||
{
|
||||
switch (XrStorageLocation)
|
||||
{
|
||||
case XR_SPACE_STORAGE_LOCATION_LOCAL_FB:
|
||||
return EOculusXRSpaceStorageLocation::Local;
|
||||
case XR_SPACE_STORAGE_LOCATION_CLOUD_FB:
|
||||
return EOculusXRSpaceStorageLocation::Cloud;
|
||||
default:
|
||||
return EOculusXRSpaceStorageLocation::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
XrSpaceStorageLocationFB ToStorageLocation(EOculusXRSpaceStorageLocation StorageLocation)
|
||||
{
|
||||
switch (StorageLocation)
|
||||
{
|
||||
case EOculusXRSpaceStorageLocation::Local:
|
||||
return XR_SPACE_STORAGE_LOCATION_LOCAL_FB;
|
||||
case EOculusXRSpaceStorageLocation::Cloud:
|
||||
return XR_SPACE_STORAGE_LOCATION_CLOUD_FB;
|
||||
default:
|
||||
return XR_SPACE_STORAGE_LOCATION_INVALID_FB;
|
||||
}
|
||||
}
|
||||
|
||||
FOculusXRUUID ToUuid(const XrUuidEXT& XrUuid)
|
||||
{
|
||||
return FOculusXRUUID(XrUuid.data);
|
||||
}
|
||||
|
||||
XrUuidEXT ToUuid(const FOculusXRUUID& Uuid)
|
||||
{
|
||||
XrUuidEXT result;
|
||||
FMemory::Memcpy(result.data, Uuid.UUIDBytes);
|
||||
return result;
|
||||
}
|
||||
|
||||
FString GetStringFromResult(EOculusXRAnchorResult::Type Result)
|
||||
{
|
||||
return UEnum::GetDisplayValueAsText(Result).ToString();
|
||||
}
|
||||
|
||||
FString ToString(EOculusXRSpaceComponentType ComponentType)
|
||||
{
|
||||
// Todo: More performant to use const strings and a case statement?
|
||||
return UEnum::GetDisplayValueAsText(ComponentType).ToString();
|
||||
}
|
||||
|
||||
FString ToString(EOculusXRSpaceStorageLocation StorageLocation)
|
||||
{
|
||||
// Todo: More performant to use const strings and a case statement?
|
||||
return UEnum::GetDisplayValueAsText(StorageLocation).ToString();
|
||||
}
|
||||
} // namespace OculusXRAnchors
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "openxr/OculusXRAnchorsXRIncludes.h"
|
||||
#include "OculusXRAnchorTypes.h"
|
||||
#include "OculusXRHMDPrivate.h"
|
||||
|
||||
namespace OculusXRAnchors
|
||||
{
|
||||
OCULUSXRANCHORS_API EOculusXRAnchorResult::Type GetResultFromOVRResult(ovrpResult OVRResult);
|
||||
OCULUSXRANCHORS_API EOculusXRAnchorResult::Type GetResultFromXrResult(XrResult Result);
|
||||
|
||||
OCULUSXRANCHORS_API EOculusXRSpaceComponentType ToComponentType(XrSpaceComponentTypeFB XrComponentType);
|
||||
OCULUSXRANCHORS_API XrSpaceComponentTypeFB ToComponentType(EOculusXRSpaceComponentType ComponentType);
|
||||
|
||||
OCULUSXRANCHORS_API EOculusXRSpaceStorageLocation ToStorageLocation(XrSpaceStorageLocationFB XrStorageLocation);
|
||||
OCULUSXRANCHORS_API XrSpaceStorageLocationFB ToStorageLocation(EOculusXRSpaceStorageLocation StorageLocation);
|
||||
|
||||
OCULUSXRANCHORS_API FOculusXRUUID ToUuid(const XrUuidEXT& XrUuid);
|
||||
OCULUSXRANCHORS_API XrUuidEXT ToUuid(const FOculusXRUUID& Uuid);
|
||||
|
||||
OCULUSXRANCHORS_API FString GetStringFromResult(EOculusXRAnchorResult::Type Result);
|
||||
OCULUSXRANCHORS_API FString ToString(EOculusXRSpaceComponentType ComponentType);
|
||||
OCULUSXRANCHORS_API FString ToString(EOculusXRSpaceStorageLocation StorageLocation);
|
||||
} // namespace OculusXRAnchors
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRSpatialAnchorComponent.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOculusSpatialAnchor);
|
||||
|
||||
UOculusXRSpatialAnchorComponent::UOculusXRSpatialAnchorComponent(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
bool UOculusXRSpatialAnchorComponent::Create(const FTransform& NewAnchorTransform, AActor* OwningActor, const FOculusXRSpatialAnchorCreateDelegate& Callback)
|
||||
{
|
||||
EOculusXRAnchorResult::Type AnchorResult;
|
||||
return OculusXRAnchors::FOculusXRAnchors::CreateSpatialAnchor(NewAnchorTransform, OwningActor, Callback, AnchorResult);
|
||||
}
|
||||
|
||||
bool UOculusXRSpatialAnchorComponent::Erase(const FOculusXRAnchorEraseDelegate& Callback)
|
||||
{
|
||||
EOculusXRAnchorResult::Type AnchorResult;
|
||||
return OculusXRAnchors::FOculusXRAnchors::EraseAnchor(this, Callback, AnchorResult);
|
||||
}
|
||||
|
||||
bool UOculusXRSpatialAnchorComponent::Save(EOculusXRSpaceStorageLocation Location, const FOculusXRAnchorSaveDelegate& Callback)
|
||||
{
|
||||
EOculusXRAnchorResult::Type AnchorResult;
|
||||
return OculusXRAnchors::FOculusXRAnchors::SaveAnchor(this, Location, Callback, AnchorResult);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRSpatialAnchorManager.h"
|
||||
#include "OculusXRAnchorBPFunctionLibrary.h"
|
||||
|
||||
namespace OculusXRAnchors
|
||||
{
|
||||
bool FOculusXRSpatialAnchorManager::CreateSpatialAnchor(const FTransform& InTransform, uint64& OutRequestId)
|
||||
{
|
||||
EOculusXRAnchorResult::Type Result = CreateAnchor(InTransform, OutRequestId, FTransform::Identity);
|
||||
return UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(Result);
|
||||
}
|
||||
} // namespace OculusXRAnchors
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "OculusXRAnchorManager.h"
|
||||
|
||||
namespace OculusXRAnchors
|
||||
{
|
||||
struct FOculusXRSpatialAnchorManager : FOculusXRAnchorManager
|
||||
{
|
||||
FOculusXRSpatialAnchorManager()
|
||||
: FOculusXRAnchorManager()
|
||||
{
|
||||
}
|
||||
|
||||
static bool CreateSpatialAnchor(const FTransform& InTransform, uint64& OutRequestId);
|
||||
};
|
||||
} // namespace OculusXRAnchors
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OculusXRTelemetry.h"
|
||||
|
||||
namespace OculusXRTelemetry::Events
|
||||
{
|
||||
using FAnchorsCreate = TMarker<191967648>;
|
||||
using FAnchorsCreateRequest = TScopedMarker<FAnchorsCreate, EScopeMode::Start>;
|
||||
using FAnchorsCreateResponse = TScopedMarker<FAnchorsCreate, EScopeMode::End>;
|
||||
using FAnchorsSetComponentStatus = TMarker<191962330>;
|
||||
using FAnchorsSetComponentStatusRequest = TScopedMarker<FAnchorsSetComponentStatus, EScopeMode::Start>;
|
||||
using FAnchorsSetComponentStatusResponse = TScopedMarker<FAnchorsSetComponentStatus, EScopeMode::End>;
|
||||
using FAnchorsSave = TMarker<191961984>;
|
||||
using FAnchorsSaveRequest = TScopedMarker<FAnchorsSave, EScopeMode::Start>;
|
||||
using FAnchorsSaveResponse = TScopedMarker<FAnchorsSave, EScopeMode::End>;
|
||||
using FAnchorsQuery = TMarker<191959258>;
|
||||
using FAnchorsQueryRequest = TScopedMarker<FAnchorsQuery, EScopeMode::Start>;
|
||||
using FAnchorsQueryResponse = TScopedMarker<FAnchorsQuery, EScopeMode::End>;
|
||||
using FAnchorsErase = TMarker<191960591>;
|
||||
using FAnchorsEraseRequest = TScopedMarker<FAnchorsErase, EScopeMode::Start>;
|
||||
using FAnchorsEraseResponse = TScopedMarker<FAnchorsErase, EScopeMode::End>;
|
||||
} // namespace OculusXRTelemetry::Events
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,110 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OculusXRAnchorsXRIncludes.h"
|
||||
#include "IOpenXRExtensionPlugin.h"
|
||||
#include "OculusXRAnchorTypes.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "OculusXRAnchors"
|
||||
|
||||
class FOpenXRHMD;
|
||||
|
||||
namespace XRAnchors
|
||||
{
|
||||
extern PFN_xrCreateSpatialAnchorFB xrCreateSpatialAnchorFB;
|
||||
extern PFN_xrSetSpaceComponentStatusFB xrSetSpaceComponentStatusFB;
|
||||
extern PFN_xrGetSpaceComponentStatusFB xrGetSpaceComponentStatusFB;
|
||||
extern PFN_xrEnumerateSpaceSupportedComponentsFB xrEnumerateSpaceSupportedComponentsFB;
|
||||
extern PFN_xrGetSpaceUuidFB xrGetSpaceUuidFB;
|
||||
extern PFN_xrGetSpaceContainerFB xrGetSpaceContainerFB;
|
||||
extern PFN_xrQuerySpacesFB xrQuerySpacesFB;
|
||||
extern PFN_xrRetrieveSpaceQueryResultsFB xrRetrieveSpaceQueryResultsFB;
|
||||
extern PFN_xrShareSpacesFB xrShareSpacesFB;
|
||||
extern PFN_xrShareSpacesMETA xrShareSpacesMETA;
|
||||
extern PFN_xrSaveSpaceFB xrSaveSpaceFB;
|
||||
extern PFN_xrEraseSpaceFB xrEraseSpaceFB;
|
||||
extern PFN_xrSaveSpaceListFB xrSaveSpaceListFB;
|
||||
extern PFN_xrCreateSpaceUserFB xrCreateSpaceUserFB;
|
||||
extern PFN_xrDestroySpaceUserFB xrDestroySpaceUserFB;
|
||||
extern PFN_xrGetSpaceUserIdFB xrGetSpaceUserIdFB;
|
||||
extern PFN_xrSaveSpacesMETA xrSaveSpacesMETA;
|
||||
extern PFN_xrEraseSpacesMETA xrEraseSpacesMETA;
|
||||
extern PFN_xrDiscoverSpacesMETA xrDiscoverSpacesMETA;
|
||||
extern PFN_xrRetrieveSpaceDiscoveryResultsMETA xrRetrieveSpaceDiscoveryResultsMETA;
|
||||
|
||||
class FAnchorsXR : public IOpenXRExtensionPlugin
|
||||
{
|
||||
public:
|
||||
// IOculusXROpenXRHMDPlugin
|
||||
virtual bool GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
|
||||
virtual bool GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
|
||||
virtual const void* OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext) override;
|
||||
virtual const void* OnCreateSession(XrInstance InInstance, XrSystemId InSystem, const void* InNext) override;
|
||||
virtual void OnDestroySession(XrSession InSession) override;
|
||||
virtual void OnEvent(XrSession InSession, const XrEventDataBaseHeader* InHeader) override;
|
||||
|
||||
public:
|
||||
FAnchorsXR();
|
||||
virtual ~FAnchorsXR();
|
||||
void RegisterAsOpenXRExtension();
|
||||
|
||||
bool IsAnchorExtensionSupported() const { return bExtAnchorsEnabled; }
|
||||
bool IsContainerExtensionSupported() const { return bExtContainerEnabled; }
|
||||
bool IsQueryExtensionSupported() const { return bExtQueryEnabled; }
|
||||
bool IsSharingExtensionSupported() const { return bExtSharingEnabled; }
|
||||
bool IsStorageExtensionSupported() const { return bExtStorageEnabled; }
|
||||
bool IsStorageBatchExtensionSupported() const { return bExtStorageBatchEnabled; }
|
||||
bool IsUserExtensionSupported() const { return bExtUserEnabled; }
|
||||
bool IsDiscoveryExtensionSupported() const { return bExtDiscoveryEnabled; }
|
||||
bool IsPersistenceExtensionSupported() const { return bExtPersistenceEnabled; }
|
||||
bool IsSharingMetaExtensionSupported() const { return bExtSharingMetaEnabled; }
|
||||
bool IsGroupSharingExtensionSupported() const { return bExtGroupSharingEnabled; }
|
||||
|
||||
XrResult CreateSpatialAnchor(const FTransform& InTransform, uint64& OutRequestId, const FTransform& CameraTransform);
|
||||
XrResult DestroySpatialAnchor(uint64 AnchorHandle);
|
||||
|
||||
XrResult TryGetAnchorTransform(uint64 AnchorHandle, FTransform& OutTransform, FOculusXRAnchorLocationFlags& OutLocationFlags, EOculusXRAnchorSpace Space);
|
||||
XrResult SetAnchorComponentStatus(uint64 AnchorHandle, EOculusXRSpaceComponentType ComponentType, bool Enable, float Timeout, uint64& OutRequestId);
|
||||
XrResult GetAnchorComponentStatus(uint64 AnchorHandle, EOculusXRSpaceComponentType ComponentType, bool& OutEnabled, bool& OutChangePending);
|
||||
XrResult GetSupportedAnchorComponents(uint64 AnchorHandle, TArray<EOculusXRSpaceComponentType>& OutSupportedTypes);
|
||||
XrResult GetAnchorContainerUUIDs(uint64 AnchorHandle, TArray<FOculusXRUUID>& OutUUIDs);
|
||||
|
||||
XrResult SaveAnchor(uint64 AnchorHandle, EOculusXRSpaceStorageLocation StorageLocation, EOculusXRSpaceStoragePersistenceMode StoragePersistenceMode, uint64& OutRequestId);
|
||||
XrResult SaveAnchorList(const TArray<uint64>& AnchorHandles, EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId);
|
||||
XrResult SaveAnchors(const TArray<uint64>& AnchorHandles, uint64& OutRequestId);
|
||||
|
||||
XrResult DiscoverAnchors(const FOculusXRSpaceDiscoveryInfo& DiscoveryInfo, uint64& OutRequestId);
|
||||
XrResult QueryAnchors(const FOculusXRSpaceQueryInfo& QueryInfo, uint64& OutRequestId);
|
||||
|
||||
XrResult ShareAnchors(const TArray<uint64>& AnchorHandles, const TArray<uint64>& UserIds, uint64& OutRequestId);
|
||||
XrResult ShareAnchorsWithGroups(const TArray<uint64>& AnchorHandles, const TArray<FOculusXRUUID>& Groups, uint64& OutRequestId);
|
||||
|
||||
XrResult CreateSpaceUser(uint64 SpaceUserId, SpaceUser& OutUser);
|
||||
XrResult GetSpaceUserId(const SpaceUser& User, uint64& OutId);
|
||||
XrResult DestroySpaceUser(const SpaceUser& User);
|
||||
|
||||
XrResult EraseAnchor(uint64 AnchorHandle, EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId);
|
||||
XrResult EraseAnchors(const TArray<FOculusXRUInt64>& AnchorHandles, const TArray<FOculusXRUUID>& UUIDs, uint64& OutRequestId);
|
||||
|
||||
private:
|
||||
void InitOpenXRFunctions(XrInstance InInstance);
|
||||
|
||||
bool bExtAnchorsEnabled;
|
||||
bool bExtContainerEnabled;
|
||||
bool bExtQueryEnabled;
|
||||
bool bExtSharingEnabled;
|
||||
bool bExtStorageEnabled;
|
||||
bool bExtStorageBatchEnabled;
|
||||
bool bExtUserEnabled;
|
||||
bool bExtDiscoveryEnabled;
|
||||
bool bExtPersistenceEnabled;
|
||||
bool bExtSharingMetaEnabled;
|
||||
bool bExtGroupSharingEnabled;
|
||||
|
||||
FOpenXRHMD* OpenXRHMD;
|
||||
};
|
||||
|
||||
} // namespace XRAnchors
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <khronos/openxr/openxr.h>
|
||||
#include <khronos/openxr/meta_openxr_preview/meta_spatial_entity_discovery.h>
|
||||
#include <khronos/openxr/meta_openxr_preview/meta_spatial_entity_persistence.h>
|
||||
#include <khronos/openxr/meta_openxr_preview/meta_spatial_entity_sharing.h>
|
||||
#include <khronos/openxr/meta_openxr_preview/meta_spatial_entity_group_sharing.h>
|
||||
@@ -0,0 +1,50 @@
|
||||
// @lint-ignore-every LICENSELINT
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "OculusXRAnchorTypes.h"
|
||||
#include "OculusXRAnchorComponents.h"
|
||||
|
||||
#define OCULUS_ANCHORS_SUPPORTED_PLATFORMS (PLATFORM_WINDOWS && WINVER > 0x0502) || (PLATFORM_ANDROID_ARM || PLATFORM_ANDROID_ARM64 || PLATFORM_ANDROID_X64)
|
||||
|
||||
class UOculusXRBaseAnchorComponent;
|
||||
|
||||
class IOculusXRCreateAnchorComponent
|
||||
{
|
||||
public:
|
||||
virtual UOculusXRBaseAnchorComponent* TryCreateAnchorComponent(uint64 AnchorHandle, EOculusXRSpaceComponentType Type, UObject* Outer) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* The public interface to this module. In most cases, this interface is only public to sibling modules
|
||||
* within this plugin.
|
||||
*/
|
||||
class IOculusXRAnchorsModule : 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 IOculusXRAnchorsModule& Get()
|
||||
{
|
||||
return FModuleManager::LoadModuleChecked<IOculusXRAnchorsModule>("OculusXRAnchors");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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("OculusXRAnchors");
|
||||
}
|
||||
|
||||
virtual void AddCreateAnchorComponentInterface(IOculusXRCreateAnchorComponent* CastInterface) = 0;
|
||||
virtual void RemoveCreateAnchorComponentInterface(IOculusXRCreateAnchorComponent* CastInterface) = 0;
|
||||
virtual UOculusXRBaseAnchorComponent* CreateAnchorComponent(uint64 AnchorHandle, EOculusXRSpaceComponentType Type, UObject* Outer) = 0;
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "Kismet/BlueprintAsyncActionBase.h"
|
||||
#include "OculusXRAnchorTypes.h"
|
||||
#include "OculusXRAnchorComponents.h"
|
||||
#include "OculusXRAnchorBPFunctionLibrary.generated.h"
|
||||
|
||||
// Helper
|
||||
UCLASS()
|
||||
class OCULUSXRANCHORS_API UOculusXRAnchorBPFunctionLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Spawn Oculus Anchor Actor", WorldContext = "WorldContextObject", UnsafeDuringActorConstruction = "true"), Category = "OculusXR|SpatialAnchor")
|
||||
static AActor* SpawnActorWithAnchorHandle(UObject* WorldContextObject, FOculusXRUInt64 Handle, FOculusXRUUID UUID, EOculusXRSpaceStorageLocation AnchorLocation, UClass* ActorClass, AActor* Owner, APawn* Instigator, ESpawnActorCollisionHandlingMethod CollisionHandlingMethod);
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Spawn Oculus Anchor Actor From Query", WorldContext = "WorldContextObject", UnsafeDuringActorConstruction = "true"), Category = "OculusXR|SpatialAnchor")
|
||||
static AActor* SpawnActorWithAnchorQueryResults(UObject* WorldContextObject, const FOculusXRSpaceQueryResult& QueryResult, UClass* ActorClass, AActor* Owner, APawn* Instigator, ESpawnActorCollisionHandlingMethod CollisionHandlingMethod);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor")
|
||||
static bool GetAnchorComponentStatus(AActor* TargetActor, EOculusXRSpaceComponentType ComponentType, bool& bIsEnabled);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor")
|
||||
static bool GetAnchorTransformByHandle(const FOculusXRUInt64& Handle, FTransform& OutTransform);
|
||||
|
||||
/**
|
||||
* Try to get the anchors transform. The transform may not always be a available.
|
||||
*
|
||||
* @param Handle The Anchor handle.
|
||||
* @param OutTransform (out) The anchors transform.
|
||||
* @param OutLocationFlags (out) The location flags.
|
||||
* @param Space The space in which this transform should be returned.
|
||||
*
|
||||
* @return Whether or not the transform could be retrieved.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor")
|
||||
static bool TryGetAnchorTransformByHandle(const FOculusXRUInt64& Handle, FTransform& OutTransform, FOculusXRAnchorLocationFlags& OutLocationFlags, EOculusXRAnchorSpace Space = EOculusXRAnchorSpace::World);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "FOculusXRUInt64 To String", CompactNodeTitle = "->", BlueprintAutocast), Category = "OculusXR|SpatialAnchor")
|
||||
static FString AnchorHandleToString(const FOculusXRUInt64 Value);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "FOculusXRUUID To String", CompactNodeTitle = "->", BlueprintAutocast), Category = "OculusXR|SpatialAnchor")
|
||||
static FString AnchorUUIDToString(const FOculusXRUUID& Value);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor")
|
||||
static FOculusXRUUID StringToAnchorUUID(const FString& Value);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "FOculusXRUInt64 equal", CompactNodeTitle = "==", Keywords = "equal", BlueprintAutocast), Category = "OculusXR|SpatialAnchor")
|
||||
static bool IsEqual_FOculusXRUInt64(const FOculusXRUInt64 Left, const FOculusXRUInt64 Right) { return Left == Right; };
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "FOculusXRUUID equal", CompactNodeTitle = "==", Keywords = "equal", BlueprintAutocast), Category = "OculusXR|SpatialAnchor")
|
||||
static bool IsEqual_FOculusXRUUID(const FOculusXRUUID& Left, const FOculusXRUUID& Right) { return Left.IsEqual(Right); };
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor")
|
||||
static bool IsAnchorResultSuccess(EOculusXRAnchorResult::Type result);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor")
|
||||
static const UOculusXRBaseAnchorComponent* GetAnchorComponent(const FOculusXRSpaceQueryResult& QueryResult, EOculusXRSpaceComponentType ComponentType, UObject* Outer);
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OculusXRAnchorTypes.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "OculusXRAnchorComponent.generated.h"
|
||||
|
||||
UCLASS(meta = (DisplayName = "Oculus Anchor Component"))
|
||||
class OCULUSXRANCHORS_API UOculusXRAnchorComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UOculusXRAnchorComponent(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
virtual void BeginPlay() override;
|
||||
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "OculusXR|Anchor", meta = (DefaultToSelf = Target))
|
||||
FOculusXRUInt64 GetHandle() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "OculusXR|Anchor", meta = (DefaultToSelf = Target))
|
||||
void SetHandle(FOculusXRUInt64 Handle);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "OculusXR|Anchor", meta = (DefaultToSelf = Target))
|
||||
bool HasValidHandle() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "OculusXR|Anchor", meta = (DefaultToSelf = Target))
|
||||
FOculusXRUUID GetUUID() const;
|
||||
|
||||
void SetUUID(FOculusXRUUID NewUUID);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "OculusXR|Anchor", meta = (DefaultToSelf = Target))
|
||||
bool IsStoredAtLocation(EOculusXRSpaceStorageLocation Location) const;
|
||||
|
||||
// Not exposed to BP because this is managed in code
|
||||
void SetStoredLocation(EOculusXRSpaceStorageLocation Location, bool Stored);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "OculusXR|Anchor", meta = (DefaultToSelf = Target))
|
||||
bool IsSaved() const;
|
||||
|
||||
protected:
|
||||
bool bUpdateHeadSpaceTransform;
|
||||
|
||||
private:
|
||||
FOculusXRUInt64 AnchorHandle;
|
||||
FOculusXRUUID AnchorUUID;
|
||||
int32 StorageLocations;
|
||||
|
||||
UPROPERTY()
|
||||
class APlayerCameraManager* PlayerCameraManager;
|
||||
|
||||
void UpdateAnchorTransform() const;
|
||||
bool ToWorldSpacePose(FTransform CameraTransform, FTransform& OutTrackingSpaceTransform) const;
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
#include "UObject/Class.h"
|
||||
#include "OculusXRAnchorTypes.h"
|
||||
#include "OculusXRAnchorComponents.generated.h"
|
||||
|
||||
UCLASS(Blueprintable)
|
||||
class OCULUSXRANCHORS_API UOculusXRBaseAnchorComponent : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
template <typename T>
|
||||
static T* FromSpace(uint64 space, UObject* Outer)
|
||||
{
|
||||
T* Component = NewObject<T>(Outer);
|
||||
Component->Space = space;
|
||||
return Component;
|
||||
}
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor")
|
||||
bool IsComponentEnabled() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor")
|
||||
EOculusXRSpaceComponentType GetType() const;
|
||||
|
||||
uint64 GetSpace() const;
|
||||
|
||||
protected:
|
||||
uint64 Space;
|
||||
EOculusXRSpaceComponentType Type = EOculusXRSpaceComponentType::Undefined;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable)
|
||||
class OCULUSXRANCHORS_API UOculusXRLocatableAnchorComponent : public UOculusXRBaseAnchorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UOculusXRLocatableAnchorComponent()
|
||||
{
|
||||
Type = EOculusXRSpaceComponentType::Locatable;
|
||||
}
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor")
|
||||
bool GetTransform(FTransform& outTransform) const;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable)
|
||||
class OCULUSXRANCHORS_API UOculusXRSpaceContainerAnchorComponent : public UOculusXRBaseAnchorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UOculusXRSpaceContainerAnchorComponent()
|
||||
{
|
||||
Type = EOculusXRSpaceComponentType::SpaceContainer;
|
||||
}
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor")
|
||||
bool GetUUIDs(TArray<FOculusXRUUID>& outUUIDs) const;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable)
|
||||
class OCULUSXRANCHORS_API UOculusXRSharableAnchorComponent : public UOculusXRBaseAnchorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UOculusXRSharableAnchorComponent()
|
||||
{
|
||||
Type = EOculusXRSpaceComponentType::Sharable;
|
||||
}
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable)
|
||||
class OCULUSXRANCHORS_API UOculusXRStorableAnchorComponent : public UOculusXRBaseAnchorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UOculusXRStorableAnchorComponent()
|
||||
{
|
||||
Type = EOculusXRSpaceComponentType::Storable;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,165 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreTypes.h"
|
||||
#include "OculusXRAnchorTypes.h"
|
||||
#include "Delegates/Delegate.h"
|
||||
|
||||
class FOculusXRAnchorEventDelegates
|
||||
{
|
||||
public:
|
||||
/* ovrpEventType_SpatialAnchorCreateComplete
|
||||
*
|
||||
* SpatialAnchorCreateComplete
|
||||
* Prefix:
|
||||
* FOculusXRSpatialAnchorCreateComplete
|
||||
* Suffix:
|
||||
* FOculusXRSpatialAnchorCreateCompleteDelegate
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_FourParams(FOculusXRSpatialAnchorCreateCompleteDelegate, FOculusXRUInt64 /*requestId*/, EOculusXRAnchorResult::Type /*result*/, FOculusXRUInt64 /*space*/, FOculusXRUUID /*uuid*/);
|
||||
static OCULUSXRANCHORS_API FOculusXRSpatialAnchorCreateCompleteDelegate OculusSpatialAnchorCreateComplete;
|
||||
|
||||
/* ovrpEventType_SpaceSetComponentStatusComplete
|
||||
*
|
||||
* SpaceSetComponentStatusComplete
|
||||
* Prefix:
|
||||
* FOculusXRSpaceSetComponentStatusComplete
|
||||
* Suffix:
|
||||
* FOculusXRSpaceSetComponentStatusCompleteDelegate
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_SixParams(FOculusXRSpaceSetComponentStatusCompleteDelegate, FOculusXRUInt64 /*requestId*/, EOculusXRAnchorResult::Type /*result*/, FOculusXRUInt64 /*space*/, FOculusXRUUID /*uuid*/, EOculusXRSpaceComponentType /*componenttype */, bool /*enabled*/);
|
||||
static OCULUSXRANCHORS_API FOculusXRSpaceSetComponentStatusCompleteDelegate OculusSpaceSetComponentStatusComplete;
|
||||
|
||||
/* ovrpEventType_SpaceQueryResults
|
||||
*
|
||||
* SpaceQueryResults
|
||||
* Prefix:
|
||||
* FOculusXRSpaceQueryResults
|
||||
* Suffix:
|
||||
* FOculusXRSpaceQueryResultsDelegate
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_OneParam(FOculusXRSpaceQueryResultsDelegate, FOculusXRUInt64 /*requestId*/);
|
||||
static OCULUSXRANCHORS_API FOculusXRSpaceQueryResultsDelegate OculusSpaceQueryResults;
|
||||
|
||||
/* SpaceQueryResult (no ovrp event type)
|
||||
*
|
||||
* SpaceQueryResult
|
||||
* Prefix:
|
||||
* FOculusXRSpaceQueryResult
|
||||
* Suffix:
|
||||
* FOculusXRSpaceQueryResultDelegate
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_ThreeParams(FOculusXRSpaceQueryResultDelegate, FOculusXRUInt64 /*requestId*/, FOculusXRUInt64 /* space*/, FOculusXRUUID /*uuid*/);
|
||||
static OCULUSXRANCHORS_API FOculusXRSpaceQueryResultDelegate OculusSpaceQueryResult;
|
||||
|
||||
/* ovrpEventType_SpaceQueryComplete
|
||||
*
|
||||
* SpaceQueryComplete
|
||||
* Prefix:
|
||||
* FOculusXRSpaceQueryComplete
|
||||
* Suffix:
|
||||
* FOculusXRSpaceQueryCompleteDelegate
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_TwoParams(FOculusXRSpaceQueryCompleteDelegate, FOculusXRUInt64 /*requestId*/, EOculusXRAnchorResult::Type /*result*/);
|
||||
static OCULUSXRANCHORS_API FOculusXRSpaceQueryCompleteDelegate OculusSpaceQueryComplete;
|
||||
|
||||
/* ovrpEventType_SpaceSaveComplete
|
||||
*
|
||||
* SpaceSaveComplete
|
||||
* Prefix:
|
||||
* FOculusXRSpaceSaveComplete
|
||||
* Suffix:
|
||||
* FOculusXRSpaceSaveCompleteDelegate
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_FiveParams(FOculusXRSpaceSaveCompleteDelegate, FOculusXRUInt64 /*requestId*/, FOculusXRUInt64 /* space*/, bool /* sucess*/, EOculusXRAnchorResult::Type /*result*/, FOculusXRUUID /*uuid*/);
|
||||
static OCULUSXRANCHORS_API FOculusXRSpaceSaveCompleteDelegate OculusSpaceSaveComplete;
|
||||
|
||||
/* ovrpEventType_SpaceListSaveResult
|
||||
*
|
||||
* SpaceListSaveComplete
|
||||
* Prefix:
|
||||
* FOculusSpaceListSaveComplete
|
||||
* Suffix:
|
||||
* FOculusSpaceListSaveCompleteDelegate
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_TwoParams(FOculusXRSpaceListSaveCompleteDelegate, FOculusXRUInt64 /*requestId*/, EOculusXRAnchorResult::Type /*result*/);
|
||||
static OCULUSXRANCHORS_API FOculusXRSpaceListSaveCompleteDelegate OculusSpaceListSaveComplete;
|
||||
|
||||
/* ovrpEventType_SpaceEraseComplete
|
||||
*
|
||||
* SpaceEraseComplete
|
||||
* Prefix:
|
||||
* FOculusXRSpaceEraseComplete
|
||||
* Suffix:
|
||||
* FOculusXRSpaceEraseCompleteDelegate
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_FourParams(FOculusXRSpaceEraseCompleteDelegate, FOculusXRUInt64 /*requestId*/, EOculusXRAnchorResult::Type /* result*/, FOculusXRUUID /*uuid*/, EOculusXRSpaceStorageLocation /*location*/);
|
||||
static OCULUSXRANCHORS_API FOculusXRSpaceEraseCompleteDelegate OculusSpaceEraseComplete;
|
||||
|
||||
/* ovrpEventType_SpaceShareSpaceResult
|
||||
*
|
||||
* SpaceShareComplete
|
||||
* Prefix:
|
||||
* FOculusSpaceShareSpacesComplete
|
||||
* Suffix:
|
||||
* FOculusSpaceShareSpacesCompleteDelegate
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_TwoParams(FOculusXRSpaceShareCompleteDelegate, FOculusXRUInt64 /*requestId*/, EOculusXRAnchorResult::Type /*result*/);
|
||||
static OCULUSXRANCHORS_API FOculusXRSpaceShareCompleteDelegate OculusSpaceShareComplete;
|
||||
|
||||
/* ovrpEventType_SpaceDiscoveryComplete
|
||||
*
|
||||
* SpaceDiscoveryComplete
|
||||
* Prefix:
|
||||
* FOculusXRSpaceDiscoveryComplete
|
||||
* Suffix:
|
||||
* FOculusXRDiscoverSpacesCompleteDelegate
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_TwoParams(FOculusXRAnchorsDiscoverCompleteDelegate, FOculusXRUInt64 /*requestId*/, EOculusXRAnchorResult::Type /*result*/);
|
||||
static OCULUSXRANCHORS_API FOculusXRAnchorsDiscoverCompleteDelegate OculusAnchorsDiscoverComplete;
|
||||
|
||||
/* ovrpEventType_SpaceDiscoveryResultsAvailable
|
||||
*
|
||||
* SpaceDiscoveryResults
|
||||
* Prefix:
|
||||
* FOculusXRAnchorsDiscoverResults
|
||||
* Suffix:
|
||||
* FOculusXRAnchorsDiscoverResultsDelegate
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_TwoParams(FOculusXRAnchorsDiscoverResultsDelegate, FOculusXRUInt64 /*requestId*/, const TArray<FOculusXRAnchorsDiscoverResult>& /*results*/);
|
||||
static OCULUSXRANCHORS_API FOculusXRAnchorsDiscoverResultsDelegate OculusAnchorsDiscoverResults;
|
||||
|
||||
/* ovrpEventType_SpacesSaveResult
|
||||
*
|
||||
* SpacesSaveComplete
|
||||
* Prefix:
|
||||
* FOculusXRAnchorsSaveComplete
|
||||
* Suffix:
|
||||
* FOculusXRAnchorsSaveCompleteDelegate
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_TwoParams(FOculusXRAnchorsSaveCompleteDelegate, FOculusXRUInt64 /*requestId*/, EOculusXRAnchorResult::Type /*result*/);
|
||||
static OCULUSXRANCHORS_API FOculusXRAnchorsSaveCompleteDelegate OculusAnchorsSaveComplete;
|
||||
|
||||
/* ovrpEventType_SpacesEraseResult
|
||||
*
|
||||
* SpacesEraseResult
|
||||
* Prefix:
|
||||
* FOculusXRAnchorsEraseComplete
|
||||
* Suffix:
|
||||
* FOculusXRAnchorsEraseCompleteDelegate
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_TwoParams(FOculusXRAnchorsEraseCompleteDelegate, FOculusXRUInt64 /*requestId*/, EOculusXRAnchorResult::Type /*result*/);
|
||||
static OCULUSXRANCHORS_API FOculusXRAnchorsEraseCompleteDelegate OculusAnchorsEraseComplete;
|
||||
|
||||
/* ovrpEventType_SpaceShareSpaceResult
|
||||
*
|
||||
* ShareAnchorsComplete
|
||||
* Prefix:
|
||||
* FOculusXRShareAnchorsComplete
|
||||
* Suffix:
|
||||
* FOculusXRShareAnchorsCompleteDelegate
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_TwoParams(FOculusXRShareAnchorsCompleteDelegate, FOculusXRUInt64 /*requestId*/, EOculusXRAnchorResult::Type /*result*/);
|
||||
static OCULUSXRANCHORS_API FOculusXRShareAnchorsCompleteDelegate OculusShareAnchorsComplete;
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OculusXRAnchorTypes.h"
|
||||
|
||||
class OCULUSXRANCHORS_API IOculusXRAnchorFunctions
|
||||
{
|
||||
public:
|
||||
virtual EOculusXRAnchorResult::Type CreateAnchor(const FTransform& InTransform, uint64& OutRequestId, const FTransform& CameraTransform) = 0;
|
||||
virtual EOculusXRAnchorResult::Type DestroyAnchor(uint64 AnchorHandle) = 0;
|
||||
|
||||
/**
|
||||
* Try to get the anchors transform. The transform may not always be a available.
|
||||
*
|
||||
* @param AnchorHandle The Anchor handle.
|
||||
* @param OutTransform (out) The anchors transform.
|
||||
* @param OutLocationFlags (out) The location flags.
|
||||
* @param Space The space in which this transform should be returned.
|
||||
*
|
||||
* @return Whether or not the transform could be retrieved.
|
||||
*/
|
||||
virtual EOculusXRAnchorResult::Type TryGetAnchorTransform(uint64 AnchorHandle, FTransform& OutTransform, FOculusXRAnchorLocationFlags& OutLocationFlags, EOculusXRAnchorSpace Space) = 0;
|
||||
virtual EOculusXRAnchorResult::Type SetAnchorComponentStatus(uint64 AnchorHandle, EOculusXRSpaceComponentType ComponentType, bool Enable, float Timeout, uint64& OutRequestId) = 0;
|
||||
virtual EOculusXRAnchorResult::Type GetAnchorComponentStatus(uint64 AnchorHandle, EOculusXRSpaceComponentType ComponentType, bool& OutEnabled, bool& OutChangePending) = 0;
|
||||
virtual EOculusXRAnchorResult::Type GetSupportedAnchorComponents(uint64 AnchorHandle, TArray<EOculusXRSpaceComponentType>& OutSupportedTypes) = 0;
|
||||
virtual EOculusXRAnchorResult::Type GetAnchorContainerUUIDs(uint64 AnchorHandle, TArray<FOculusXRUUID>& OutUUIDs) = 0;
|
||||
|
||||
virtual EOculusXRAnchorResult::Type SaveAnchor(uint64 AnchorHandle, EOculusXRSpaceStorageLocation StorageLocation, EOculusXRSpaceStoragePersistenceMode StoragePersistenceMode, uint64& OutRequestId) = 0;
|
||||
virtual EOculusXRAnchorResult::Type SaveAnchorList(const TArray<uint64>& AnchorHandles, EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId) = 0;
|
||||
virtual EOculusXRAnchorResult::Type SaveAnchors(const TArray<uint64>& AnchorHandles, uint64& OutRequestId) = 0;
|
||||
|
||||
virtual EOculusXRAnchorResult::Type DiscoverAnchors(const FOculusXRSpaceDiscoveryInfo& DiscoveryInfo, uint64& OutRequestId) = 0;
|
||||
virtual EOculusXRAnchorResult::Type QueryAnchors(const FOculusXRSpaceQueryInfo& QueryInfo, uint64& OutRequestId) = 0;
|
||||
virtual EOculusXRAnchorResult::Type ShareAnchors(const TArray<uint64>& AnchorHandles, const TArray<uint64>& UserIds, uint64& OutRequestId) = 0;
|
||||
virtual EOculusXRAnchorResult::Type ShareAnchors(const TArray<uint64>& AnchorHandles, const TArray<FOculusXRUUID>& Groups, uint64& OutRequestId) = 0;
|
||||
|
||||
virtual EOculusXRAnchorResult::Type EraseAnchor(uint64 AnchorHandle, EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId) = 0;
|
||||
virtual EOculusXRAnchorResult::Type EraseAnchors(const TArray<FOculusXRUInt64>& AnchorHandles, const TArray<FOculusXRUUID>& UUIDs, uint64& OutRequestId) = 0;
|
||||
};
|
||||
@@ -0,0 +1,445 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Kismet/BlueprintAsyncActionBase.h"
|
||||
#include "Templates/SharedPointer.h"
|
||||
#include "OculusXRAnchors.h"
|
||||
#include "OculusXRAnchorTypes.h"
|
||||
#include "OculusXRAnchorComponent.h"
|
||||
#include "OculusXRAnchorComponents.h"
|
||||
#include "OculusXRAnchorLatentActions.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOculusXR_LatentAction_CreateSpatialAnchor_Success, UOculusXRAnchorComponent*, Anchor, EOculusXRAnchorResult::Type, Result);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusXR_LatentAction_CreateSpatialAnchor_Failure, EOculusXRAnchorResult::Type, Result);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOculusXR_LatentAction_EraseAnchor_Success, AActor*, Actor, FOculusXRUUID, UUID, EOculusXRAnchorResult::Type, Result);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusXR_LatentAction_EraseAnchor_Failure, EOculusXRAnchorResult::Type, Result);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOculusXR_LatentAction_SaveAnchor_Success, UOculusXRAnchorComponent*, Anchor, EOculusXRAnchorResult::Type, Result);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusXR_LatentAction_SaveAnchor_Failure, EOculusXRAnchorResult::Type, Result);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOculusXR_LatentAction_SaveAnchorList_Success, const TArray<UOculusXRAnchorComponent*>&, Anchors, EOculusXRAnchorResult::Type, Result);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusXR_LatentAction_SaveAnchorList_Failure, EOculusXRAnchorResult::Type, Result);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOculusXR_LatentAction_QueryAnchors_Success, const TArray<FOculusXRSpaceQueryResult>&, QueryResults, EOculusXRAnchorResult::Type, Result);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusXR_LatentAction_QueryAnchors_Failure, EOculusXRAnchorResult::Type, Result);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FOculusXR_LatentAction_SetComponentStatus_Success, UOculusXRAnchorComponent*, Anchor, EOculusXRSpaceComponentType, ComponentType, bool, Enabled, EOculusXRAnchorResult::Type, Result);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusXR_LatentAction_SetComponentStatus_Failure, EOculusXRAnchorResult::Type, Result);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOculusXR_LatentAction_SetAnchorComponentStatus_Success, UOculusXRBaseAnchorComponent*, Component, EOculusXRAnchorResult::Type, Result);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusXR_LatentAction_SetAnchorComponentStatus_Failure, EOculusXRAnchorResult::Type, Result);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOculusXR_LatentAction_ShareAnchors_Success, const TArray<UOculusXRAnchorComponent*>&, SharedAnchors, const TArray<FString>&, UserIds, EOculusXRAnchorResult::Type, Result);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusXR_LatentAction_ShareAnchors_Failure, EOculusXRAnchorResult::Type, Result);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOculusXR_LatentAction_SaveAnchors_Success, const TArray<UOculusXRAnchorComponent*>&, Anchors, EOculusXRAnchorResult::Type, Result);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusXR_LatentAction_SaveAnchors_Failure, EOculusXRAnchorResult::Type, Result);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FOculusXR_LatentAction_EraseAnchors_Success, const TArray<UOculusXRAnchorComponent*>&, Anchors, const TArray<FOculusXRUInt64>&, AnchorHandles, const TArray<FOculusXRUUID>&, UUIDs, EOculusXRAnchorResult::Type, Result);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusXR_LatentAction_EraseAnchors_Failure, EOculusXRAnchorResult::Type, Result);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusXR_LatentAction_DiscoverAnchors_Discovered, const TArray<FOculusXRAnchorsDiscoverResult>&, DiscoveryResult);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusXR_LatentAction_DiscoverAnchors_Complete, EOculusXRAnchorResult::Type, Result);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusXR_LatentAction_DiscoverAnchors_Failure, EOculusXRAnchorResult::Type, Result);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOculusXR_LatentAction_GetSharedAnchors_Success, const TArray<FOculusXRAnchorsDiscoverResult>&, SharedAnchors, EOculusXRAnchorResult::Type, Result);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusXR_LatentAction_GetSharedAnchors_Failure, EOculusXRAnchorResult::Type, Result);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FOculusXR_LatentAction_ShareAnchorsWithGroups_Complete, bool, Success, const TArray<FOculusXRUUID>&, Groups, const TArray<FOculusXRUInt64>&, AnchorHandles, EOculusXRAnchorResult::Type, Result);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOculusXR_LatentAction_GetSharedAnchorsFromGroup_Complete, bool, Success, const TArray<FOculusXRAnchor>&, Anchors, EOculusXRAnchorResult::Type, Result);
|
||||
|
||||
//
|
||||
// Create Anchor
|
||||
//
|
||||
UCLASS()
|
||||
class OCULUSXRANCHORS_API UOculusXRAsyncAction_CreateSpatialAnchor : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
virtual void Activate() override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))
|
||||
static UOculusXRAsyncAction_CreateSpatialAnchor* OculusXRAsyncCreateSpatialAnchor(AActor* TargetActor, const FTransform& AnchorTransform);
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_CreateSpatialAnchor_Success Success;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_CreateSpatialAnchor_Failure Failure;
|
||||
|
||||
// Target actor
|
||||
UPROPERTY(Transient)
|
||||
AActor* TargetActor;
|
||||
|
||||
FTransform AnchorTransform;
|
||||
|
||||
private:
|
||||
void HandleCreateComplete(EOculusXRAnchorResult::Type CreateResult, UOculusXRAnchorComponent* Anchor);
|
||||
};
|
||||
|
||||
//
|
||||
// Erase Anchor
|
||||
//
|
||||
UCLASS()
|
||||
class OCULUSXRANCHORS_API UOculusXRAsyncAction_EraseAnchor : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
virtual void Activate() override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))
|
||||
static UOculusXRAsyncAction_EraseAnchor* OculusXRAsyncEraseAnchor(AActor* TargetActor);
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_EraseAnchor_Success Success;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_EraseAnchor_Failure Failure;
|
||||
|
||||
// Target actor
|
||||
UPROPERTY(Transient)
|
||||
AActor* TargetActor;
|
||||
|
||||
FOculusXRUInt64 DeleteRequestId;
|
||||
|
||||
private:
|
||||
void HandleEraseAnchorComplete(EOculusXRAnchorResult::Type EraseResult, FOculusXRUUID UUID);
|
||||
};
|
||||
|
||||
//
|
||||
// Save Anchor
|
||||
//
|
||||
UCLASS()
|
||||
class OCULUSXRANCHORS_API UOculusXRAsyncAction_SaveAnchor : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
virtual void Activate() override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))
|
||||
static UOculusXRAsyncAction_SaveAnchor* OculusXRAsyncSaveAnchor(AActor* TargetActor, EOculusXRSpaceStorageLocation StorageLocation);
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_SaveAnchor_Success Success;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_SaveAnchor_Failure Failure;
|
||||
|
||||
// Target actor
|
||||
UPROPERTY(Transient)
|
||||
AActor* TargetActor;
|
||||
|
||||
EOculusXRSpaceStorageLocation StorageLocation;
|
||||
|
||||
private:
|
||||
void HandleSaveAnchorComplete(EOculusXRAnchorResult::Type SaveResult, UOculusXRAnchorComponent* Anchor);
|
||||
};
|
||||
|
||||
//
|
||||
// Save Anchor List
|
||||
//
|
||||
UCLASS()
|
||||
class OCULUSXRANCHORS_API UOculusXRAsyncAction_SaveAnchorList : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
virtual void Activate() override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))
|
||||
static UOculusXRAsyncAction_SaveAnchorList* OculusXRAsyncSaveAnchorList(const TArray<AActor*>& TargetActors, EOculusXRSpaceStorageLocation StorageLocation);
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_SaveAnchorList_Success Success;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_SaveAnchorList_Failure Failure;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TArray<UOculusXRAnchorComponent*> TargetAnchors;
|
||||
|
||||
EOculusXRSpaceStorageLocation StorageLocation;
|
||||
|
||||
private:
|
||||
void HandleSaveAnchorListComplete(EOculusXRAnchorResult::Type SaveResult, const TArray<UOculusXRAnchorComponent*>& SavedSpaces);
|
||||
};
|
||||
|
||||
//
|
||||
// Query Anchors
|
||||
//
|
||||
UCLASS()
|
||||
class OCULUSXRANCHORS_API UOculusXRAsyncAction_QueryAnchors : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
virtual void Activate() override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))
|
||||
static UOculusXRAsyncAction_QueryAnchors* OculusXRAsyncQueryAnchors(EOculusXRSpaceStorageLocation Location, const TArray<FOculusXRUUID>& UUIDs);
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))
|
||||
static UOculusXRAsyncAction_QueryAnchors* OculusXRAsyncQueryAnchorsAdvanced(const FOculusXRSpaceQueryInfo& QueryInfo);
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_QueryAnchors_Success Success;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_QueryAnchors_Failure Failure;
|
||||
|
||||
FOculusXRSpaceQueryInfo QueryInfo;
|
||||
TArray<FOculusXRSpaceQueryResult> QueryResults;
|
||||
|
||||
private:
|
||||
void HandleQueryAnchorsResults(EOculusXRAnchorResult::Type QueryResult, const TArray<FOculusXRSpaceQueryResult>& Results);
|
||||
};
|
||||
|
||||
//
|
||||
// Set Component Status
|
||||
//
|
||||
UCLASS()
|
||||
class OCULUSXRANCHORS_API UOculusXRAsyncAction_SetAnchorComponentStatus : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
virtual void Activate() override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))
|
||||
static UOculusXRAsyncAction_SetAnchorComponentStatus* OculusXRAsyncSetAnchorComponentStatus(AActor* TargetActor, EOculusXRSpaceComponentType ComponentType, bool bEnabled);
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_SetComponentStatus_Success Success;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_SetComponentStatus_Failure Failure;
|
||||
|
||||
// Target actor
|
||||
UPROPERTY(Transient)
|
||||
AActor* TargetActor;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
UOculusXRAnchorComponent* TargetAnchorComponent;
|
||||
|
||||
EOculusXRSpaceComponentType ComponentType;
|
||||
bool bEnabled;
|
||||
|
||||
private:
|
||||
void HandleSetComponentStatusComplete(EOculusXRAnchorResult::Type SetStatusResult, uint64 AnchorHandle, EOculusXRSpaceComponentType SpaceComponentType, bool bResultEnabled);
|
||||
};
|
||||
|
||||
//
|
||||
// Set Anchor Component Status
|
||||
//
|
||||
UCLASS()
|
||||
class OCULUSXRANCHORS_API UOculusXRAsyncAction_SetComponentStatus : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
virtual void Activate() override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))
|
||||
static UOculusXRAsyncAction_SetComponentStatus* OculusXRAsyncSetComponentStatus(UOculusXRBaseAnchorComponent* Component, bool bEnabled);
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_SetAnchorComponentStatus_Success Success;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_SetAnchorComponentStatus_Failure Failure;
|
||||
|
||||
// Target actor
|
||||
UPROPERTY(Transient)
|
||||
UOculusXRBaseAnchorComponent* Component;
|
||||
bool bEnabled;
|
||||
|
||||
private:
|
||||
void HandleSetComponentStatusComplete(EOculusXRAnchorResult::Type SetStatusResult, uint64 AnchorHandle, EOculusXRSpaceComponentType SpaceComponentType, bool bResultEnabled);
|
||||
};
|
||||
|
||||
//
|
||||
// Share Anchors
|
||||
//
|
||||
UCLASS()
|
||||
class OCULUSXRANCHORS_API UOculusXRAsyncAction_ShareAnchors : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
virtual void Activate() override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))
|
||||
static UOculusXRAsyncAction_ShareAnchors* OculusXRAsyncShareAnchors(const TArray<AActor*>& TargetActors, const TArray<FString>& ToShareWithIds);
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_ShareAnchors_Success Success;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_ShareAnchors_Failure Failure;
|
||||
|
||||
// Target Spaces
|
||||
UPROPERTY(Transient)
|
||||
TArray<UOculusXRAnchorComponent*> TargetAnchors;
|
||||
|
||||
// Users to share with
|
||||
TArray<uint64> ToShareWithIds;
|
||||
|
||||
FOculusXRUInt64 ShareSpacesRequestId;
|
||||
|
||||
private:
|
||||
void HandleShareAnchorsComplete(EOculusXRAnchorResult::Type ShareResult, const TArray<UOculusXRAnchorComponent*>& TargetAnchors, const TArray<uint64>& OculusUserIds);
|
||||
};
|
||||
|
||||
//
|
||||
// Save Anchors
|
||||
//
|
||||
UCLASS()
|
||||
class OCULUSXRANCHORS_API UOculusXRAsyncAction_SaveAnchors : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
virtual void Activate() override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))
|
||||
static UOculusXRAsyncAction_SaveAnchors* OculusXRAsyncSaveAnchors(const TArray<AActor*>& TargetActors);
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_SaveAnchors_Success Success;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_SaveAnchors_Failure Failure;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TArray<UOculusXRAnchorComponent*> TargetAnchors;
|
||||
|
||||
private:
|
||||
void HandleSaveAnchorsComplete(EOculusXRAnchorResult::Type SaveResult, const TArray<UOculusXRAnchorComponent*>& SavedSpaces);
|
||||
};
|
||||
|
||||
//
|
||||
// Erase Anchors
|
||||
//
|
||||
UCLASS()
|
||||
class OCULUSXRANCHORS_API UOculusXRAsyncAction_EraseAnchors : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
virtual void Activate() override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", AutoCreateRefTerm = "TargetActors,AnchorHandles,AnchorUUIDs"))
|
||||
static UOculusXRAsyncAction_EraseAnchors* OculusXRAsyncEraseAnchors(const TArray<AActor*>& TargetActors, const TArray<FOculusXRUInt64>& AnchorHandles, const TArray<FOculusXRUUID>& AnchorUUIDs);
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_EraseAnchors_Success Success;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_EraseAnchors_Failure Failure;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TArray<UOculusXRAnchorComponent*> TargetAnchors;
|
||||
|
||||
TArray<FOculusXRUInt64> TargetAnchorHandles;
|
||||
TArray<FOculusXRUUID> TargetUUIDs;
|
||||
|
||||
private:
|
||||
void HandleEraseAnchorsComplete(EOculusXRAnchorResult::Type EraseResult, const TArray<UOculusXRAnchorComponent*>& ErasedAnchorComponents, const TArray<FOculusXRUInt64>& ErasedAnchorHandles, const TArray<FOculusXRUUID>& ErasedAnchorUUIDs);
|
||||
};
|
||||
|
||||
//
|
||||
// Anchors Discovery
|
||||
//
|
||||
UCLASS()
|
||||
class OCULUSXRANCHORS_API UOculusXRAsyncAction_DiscoverAnchors : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
virtual void Activate() override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))
|
||||
static UOculusXRAsyncAction_DiscoverAnchors* OculusXRAsyncDiscoverAnchors(const FOculusXRSpaceDiscoveryInfo& DiscoveryInfo);
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_DiscoverAnchors_Discovered Discovered;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_DiscoverAnchors_Complete Complete;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_DiscoverAnchors_Failure Failure;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
FOculusXRSpaceDiscoveryInfo DiscoveryInfo;
|
||||
|
||||
private:
|
||||
void HandleDiscoverResult(const TArray<FOculusXRAnchorsDiscoverResult>& DiscoveredAnchors);
|
||||
void HandleDiscoverComplete(EOculusXRAnchorResult::Type CompleteResult);
|
||||
};
|
||||
|
||||
//
|
||||
// Get Shared Anchors
|
||||
//
|
||||
UCLASS()
|
||||
class OCULUSXRANCHORS_API UOculusXRAsyncAction_GetSharedAnchors : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
virtual void Activate() override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))
|
||||
static UOculusXRAsyncAction_GetSharedAnchors* OculusXRAsyncGetSharedAnchors(const TArray<FOculusXRUUID>& AnchorUUIDs);
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_GetSharedAnchors_Success Success;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_GetSharedAnchors_Failure Failure;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TArray<FOculusXRUUID> Anchors;
|
||||
|
||||
private:
|
||||
void HandleGetSharedAnchorsResult(EOculusXRAnchorResult::Type Result, const TArray<FOculusXRAnchorsDiscoverResult>& SharedAnchors);
|
||||
};
|
||||
|
||||
//
|
||||
// Share with groups
|
||||
//
|
||||
UCLASS()
|
||||
class OCULUSXRANCHORS_API UOculusXRAsyncAction_ShareAnchorsWithGroups : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
virtual void Activate() override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))
|
||||
static UOculusXRAsyncAction_ShareAnchorsWithGroups* OculusXRShareAnchorsWithGroupsAsync(const TArray<FOculusXRUUID>& GroupUUIDs, const TArray<FOculusXRUInt64>& AnchorHandles);
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_ShareAnchorsWithGroups_Complete Complete;
|
||||
|
||||
TArray<FOculusXRUUID> GroupUUIDs;
|
||||
TArray<FOculusXRUInt64> AnchorHandles;
|
||||
|
||||
private:
|
||||
void HandleShareComplete(const OculusXRAnchors::FShareAnchorsWithGroups::FResultType& Result);
|
||||
};
|
||||
|
||||
//
|
||||
// Get shared anchors from groups
|
||||
//
|
||||
UCLASS()
|
||||
class OCULUSXRANCHORS_API UOculusXRAsyncAction_GetSharedAnchorsFromGroup : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
virtual void Activate() override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", AutoCreateRefTerm = "GroupUUIDs,AnchorUUIDs"))
|
||||
static UOculusXRAsyncAction_GetSharedAnchorsFromGroup* OculusXRGetSharedAnchorsFromGroupAsync(const FOculusXRUUID& GroupUUIDs, const TArray<FOculusXRUUID>& AnchorUUIDs);
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXR_LatentAction_GetSharedAnchorsFromGroup_Complete Complete;
|
||||
|
||||
FOculusXRUUID GroupUuid;
|
||||
TArray<FOculusXRUUID> Anchors;
|
||||
|
||||
private:
|
||||
void HandleGetSharedAnchorsComplete(const OculusXRAnchors::FGetAnchorsSharedWithGroup::FResultType& Result);
|
||||
};
|
||||
@@ -0,0 +1,432 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "OculusXRAnchorTypes.generated.h"
|
||||
|
||||
#define OCULUSXR_UUID_SIZE 16
|
||||
|
||||
typedef uint8 UuidArray[OCULUSXR_UUID_SIZE];
|
||||
typedef uint64 SpaceUser;
|
||||
|
||||
UENUM(BlueprintType)
|
||||
namespace EOculusXRAnchorResult
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
Success = 0,
|
||||
Success_EventUnavailable = 1,
|
||||
Success_Pending = 2,
|
||||
|
||||
/// Failure
|
||||
Failure = -1000,
|
||||
Failure_InvalidParameter = -1001,
|
||||
Failure_NotInitialized = -1002,
|
||||
Failure_InvalidOperation = -1003,
|
||||
Failure_Unsupported = -1004,
|
||||
Failure_NotYetImplemented = -1005,
|
||||
Failure_OperationFailed = -1006,
|
||||
Failure_InsufficientSize = -1007,
|
||||
Failure_DataIsInvalid = -1008,
|
||||
Failure_DeprecatedOperation = -1009,
|
||||
Failure_ErrorLimitReached = -1010,
|
||||
Failure_ErrorInitializationFailed = -1011,
|
||||
|
||||
/// Space error cases
|
||||
Failure_SpaceCloudStorageDisabled = -2000,
|
||||
Failure_SpaceMappingInsufficient = -2001,
|
||||
Failure_SpaceLocalizationFailed = -2002,
|
||||
Failure_SpaceNetworkTimeout = -2003,
|
||||
Failure_SpaceNetworkRequestFailed = -2004,
|
||||
|
||||
/// APD warnings and error cases
|
||||
Failure_SpaceInsufficientResources = -9000,
|
||||
Failure_SpaceStorageAtCapacity = -9001,
|
||||
Failure_SpaceInsufficientView = -9002,
|
||||
Failure_SpacePermissionInsufficient = -9003,
|
||||
Failure_SpaceRateLimited = -9004,
|
||||
Failure_SpaceTooDark = -9005,
|
||||
Failure_SpaceTooBright = -9006,
|
||||
|
||||
// Boundary visibility
|
||||
Warning_BoundaryVisibilitySuppressionNotAllowed = 9030,
|
||||
};
|
||||
} // namespace EOculusXRAnchorResult
|
||||
|
||||
UENUM(BlueprintType, meta = (Bitflags))
|
||||
enum class EOculusLocationFlags : uint8
|
||||
{
|
||||
None = 0, // required for the metadata generation
|
||||
OrientationValid = (1 << 0),
|
||||
PositionValid = (1 << 1),
|
||||
OrientationTracked = (1 << 2),
|
||||
PositionTracked = (1 << 3)
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct OCULUSXRANCHORS_API FOculusXRAnchorLocationFlags
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
FOculusXRAnchorLocationFlags(uint32 InFlags = 0)
|
||||
: Flags(InFlags) {}
|
||||
|
||||
bool OrientationValid() const
|
||||
{
|
||||
return Flags & static_cast<uint32>(EOculusLocationFlags::OrientationValid);
|
||||
}
|
||||
|
||||
bool PositionValid() const
|
||||
{
|
||||
return Flags & static_cast<uint32>(EOculusLocationFlags::PositionValid);
|
||||
}
|
||||
|
||||
bool OrientationTracked() const
|
||||
{
|
||||
return Flags & static_cast<uint32>(EOculusLocationFlags::OrientationTracked);
|
||||
}
|
||||
|
||||
bool PositionTracked() const
|
||||
{
|
||||
return Flags & static_cast<uint32>(EOculusLocationFlags::PositionTracked);
|
||||
}
|
||||
|
||||
bool IsValid() const
|
||||
{
|
||||
return OrientationValid() && PositionValid();
|
||||
}
|
||||
|
||||
private:
|
||||
UPROPERTY(BlueprintReadOnly, Category = "OculusXR|SpatialAnchor", meta = (AllowPrivateAccess = "true", Bitmask, BitmaskEnum = "EOculusLocationFlags"))
|
||||
int32 Flags;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct OCULUSXRANCHORS_API FOculusXRUUID
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FOculusXRUUID();
|
||||
FOculusXRUUID(const UuidArray& In);
|
||||
|
||||
bool operator==(const FOculusXRUUID& Other) const;
|
||||
bool operator!=(const FOculusXRUUID& Other) const;
|
||||
|
||||
bool IsValidUUID() const;
|
||||
|
||||
bool IsEqual(const FOculusXRUUID& Other) const;
|
||||
friend uint32 GetTypeHash(const FOculusXRUUID& Other) { return FCrc::MemCrc32(&Other.UUIDBytes, sizeof(Other.UUIDBytes)); }
|
||||
bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);
|
||||
|
||||
OCULUSXRANCHORS_API friend FArchive& operator<<(FArchive& Ar, FOculusXRUUID& UUID);
|
||||
bool Serialize(FArchive& Ar);
|
||||
|
||||
FString ToString() const;
|
||||
|
||||
uint8 UUIDBytes[OCULUSXR_UUID_SIZE];
|
||||
};
|
||||
|
||||
template <>
|
||||
struct TStructOpsTypeTraits<FOculusXRUUID> : public TStructOpsTypeTraitsBase2<FOculusXRUUID>
|
||||
{
|
||||
enum
|
||||
{
|
||||
WithIdenticalViaEquality = true,
|
||||
WithNetSerializer = true,
|
||||
WithSerializer = true
|
||||
};
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct OCULUSXRANCHORS_API FOculusXRUInt64
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FOculusXRUInt64()
|
||||
: FOculusXRUInt64(0) {}
|
||||
FOculusXRUInt64(const uint64& Value) { this->Value = Value; }
|
||||
|
||||
operator uint64() const { return Value; }
|
||||
bool operator==(const FOculusXRUInt64& Right) const;
|
||||
bool operator!=(const FOculusXRUInt64& Right) const;
|
||||
|
||||
UPROPERTY()
|
||||
uint64 Value;
|
||||
|
||||
bool IsEqual(const FOculusXRUInt64& Other) const
|
||||
{
|
||||
return Other.Value == Value;
|
||||
}
|
||||
|
||||
friend uint32 GetTypeHash(const FOculusXRUInt64& Other)
|
||||
{
|
||||
return FCrc::MemCrc_DEPRECATED(&Other.Value, sizeof(Other.Value));
|
||||
}
|
||||
|
||||
uint64 GetValue() const { return Value; };
|
||||
|
||||
void SetValue(const uint64 Val) { Value = Val; };
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct OCULUSXRANCHORS_API FOculusXRAnchor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
FOculusXRAnchor()
|
||||
: AnchorHandle(0), Uuid() {}
|
||||
FOculusXRAnchor(FOculusXRUInt64 SpaceHandle, FOculusXRUUID ID)
|
||||
: AnchorHandle(SpaceHandle), Uuid(ID) {}
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor")
|
||||
FOculusXRUInt64 AnchorHandle;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor")
|
||||
FOculusXRUUID Uuid;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct TStructOpsTypeTraits<FOculusXRUInt64> : public TStructOpsTypeTraitsBase2<FOculusXRUInt64>
|
||||
{
|
||||
enum
|
||||
{
|
||||
WithIdenticalViaEquality = true,
|
||||
};
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EOculusXRSpaceQueryFilterType : uint8
|
||||
{
|
||||
None = 0 UMETA(DisplayName = "No Filter"),
|
||||
FilterByIds = 1 UMETA(DisplayName = "Filter queries by UUIDs"),
|
||||
FilterByComponentType = 2 UMETA(DisplayName = "Filter queries by component type"),
|
||||
FilterByGroup = 3 UMETA(DisplayName = "Filter queries by group UUID")
|
||||
};
|
||||
|
||||
// This is used as a bit-mask
|
||||
UENUM(BlueprintType)
|
||||
enum class EOculusXRSpaceStorageLocation : uint8
|
||||
{
|
||||
Invalid = 0 UMETA(DisplayName = "Invalid"),
|
||||
Local = 1 << 0 UMETA(DisplayName = "Local"),
|
||||
Cloud = 1 << 1 UMETA(DisplayName = "Cloud")
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EOculusXRSpaceStoragePersistenceMode : uint8
|
||||
{
|
||||
Invalid = 0 UMETA(Hidden),
|
||||
Indefinite = 1 UMETA(DisplayName = "Indefinite"),
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EOculusXRSpaceComponentType : uint8
|
||||
{
|
||||
Locatable = 0 UMETA(DisplayName = "Locatable"),
|
||||
Storable = 1 UMETA(DisplayName = "Storable"),
|
||||
Sharable = 2 UMETA(DisplayName = "Sharable"),
|
||||
ScenePlane = 3 UMETA(DisplayName = "ScenePlane"),
|
||||
SceneVolume = 4 UMETA(DisplayName = "SceneVolume"),
|
||||
SemanticClassification = 5 UMETA(DisplayName = "SemanticClassification"),
|
||||
RoomLayout = 6 UMETA(DisplayName = "RoomLayout"),
|
||||
SpaceContainer = 7 UMETA(DisplayName = "SpaceContainer"),
|
||||
Undefined = 8 UMETA(DisplayName = "Not defined"),
|
||||
TriangleMesh = 9 UMETA(DisplayName = "TriangleMesh"),
|
||||
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct OCULUSXRANCHORS_API FOculusXRSpaceQueryInfo
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
FOculusXRSpaceQueryInfo()
|
||||
: MaxQuerySpaces(1024), Timeout(0), Location(EOculusXRSpaceStorageLocation::Local), FilterType(EOculusXRSpaceQueryFilterType::None)
|
||||
{
|
||||
}
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor")
|
||||
int MaxQuerySpaces;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor")
|
||||
float Timeout;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor")
|
||||
EOculusXRSpaceStorageLocation Location;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor")
|
||||
EOculusXRSpaceQueryFilterType FilterType;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor")
|
||||
TArray<FOculusXRUUID> IDFilter;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor")
|
||||
TArray<EOculusXRSpaceComponentType> ComponentFilter;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor")
|
||||
FOculusXRUUID GroupUUIDFilter;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct OCULUSXRANCHORS_API FOculusXRSpaceQueryResult
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
FOculusXRSpaceQueryResult()
|
||||
: Space(0), UUID(), Location(EOculusXRSpaceStorageLocation::Invalid) {}
|
||||
FOculusXRSpaceQueryResult(FOculusXRUInt64 SpaceHandle, FOculusXRUUID ID, EOculusXRSpaceStorageLocation SpaceLocation)
|
||||
: Space(SpaceHandle), UUID(ID), Location(SpaceLocation) {}
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor")
|
||||
FOculusXRUInt64 Space;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor")
|
||||
FOculusXRUUID UUID;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor")
|
||||
EOculusXRSpaceStorageLocation Location;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct OCULUSXRANCHORS_API FOculusXRSpaceQueryFilterValues
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
TArray<FOculusXRUUID> Uuids; // used if filtering by UUIDs
|
||||
TArray<EOculusXRSpaceComponentType> ComponentTypes; // used if filtering by component types
|
||||
};
|
||||
|
||||
struct ovrpSpaceDiscoveryFilterHeader_;
|
||||
typedef ovrpSpaceDiscoveryFilterHeader_ ovrpSpaceDiscoveryFilterHeader;
|
||||
|
||||
UCLASS(BlueprintType)
|
||||
class OCULUSXRANCHORS_API UOculusXRSpaceDiscoveryFilterBase : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
virtual const ovrpSpaceDiscoveryFilterHeader* GenerateOVRPFilter()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct OCULUSXRANCHORS_API FOculusXRSpaceDiscoveryInfo
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
FOculusXRSpaceDiscoveryInfo()
|
||||
{
|
||||
}
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor")
|
||||
TArray<UOculusXRSpaceDiscoveryFilterBase*> Filters;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct OCULUSXRANCHORS_API FOculusXRAnchorsDiscoverResult
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
FOculusXRAnchorsDiscoverResult()
|
||||
: Space(0), UUID() {}
|
||||
FOculusXRAnchorsDiscoverResult(FOculusXRUInt64 SpaceHandle, FOculusXRUUID ID)
|
||||
: Space(SpaceHandle), UUID(ID) {}
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor")
|
||||
FOculusXRUInt64 Space;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor")
|
||||
FOculusXRUUID UUID;
|
||||
};
|
||||
|
||||
struct ovrpSpaceDiscoveryFilterIds_;
|
||||
typedef ovrpSpaceDiscoveryFilterIds_ ovrpSpaceDiscoveryFilterIds;
|
||||
struct ovrpSpaceDiscoveryFilterIdsDelete
|
||||
{
|
||||
void operator()(ovrpSpaceDiscoveryFilterIds* ptr) const;
|
||||
};
|
||||
|
||||
struct DiscoveryUuidWrapper
|
||||
{
|
||||
unsigned char data[16];
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, Category = "OculusXR|SpatialAnchor")
|
||||
class OCULUSXRANCHORS_API UOculusXRSpaceDiscoveryIdsFilter : public UOculusXRSpaceDiscoveryFilterBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor")
|
||||
TArray<FOculusXRUUID> Uuids;
|
||||
|
||||
TArray<DiscoveryUuidWrapper> wrappedUUIDs;
|
||||
|
||||
virtual const ovrpSpaceDiscoveryFilterHeader* GenerateOVRPFilter() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<ovrpSpaceDiscoveryFilterIds, ovrpSpaceDiscoveryFilterIdsDelete> OVRPFilterIds;
|
||||
};
|
||||
|
||||
struct ovrpSpaceDiscoveryFilterComponents_;
|
||||
typedef ovrpSpaceDiscoveryFilterComponents_ ovrpSpaceDiscoveryFilterComponents;
|
||||
struct ovrpSpaceDiscoveryFilterComponentsDelete
|
||||
{
|
||||
void operator()(ovrpSpaceDiscoveryFilterComponents* ptr) const;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, Category = "OculusXR|SpatialAnchor")
|
||||
class OCULUSXRANCHORS_API UOculusXRSpaceDiscoveryComponentsFilter : public UOculusXRSpaceDiscoveryFilterBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor")
|
||||
EOculusXRSpaceComponentType ComponentType;
|
||||
|
||||
virtual const ovrpSpaceDiscoveryFilterHeader* GenerateOVRPFilter() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<ovrpSpaceDiscoveryFilterComponents, ovrpSpaceDiscoveryFilterComponentsDelete> OVRPFilterComponent;
|
||||
};
|
||||
|
||||
// Represents a room layout within a specific space
|
||||
USTRUCT(BlueprintType)
|
||||
struct OCULUSXRANCHORS_API FOculusXRRoomLayout
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Anchors")
|
||||
FOculusXRUInt64 RoomAnchorHandle;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Anchors")
|
||||
FOculusXRUUID RoomUuid;
|
||||
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Anchors")
|
||||
FOculusXRUUID FloorUuid;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Anchors")
|
||||
FOculusXRUUID CeilingUuid;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Anchors")
|
||||
TArray<FOculusXRUUID> WallsUuid;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Anchors")
|
||||
TArray<FOculusXRUUID> RoomObjectUUIDs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents different types of Anchor space.
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EOculusXRAnchorSpace : uint8
|
||||
{
|
||||
/** World space is relative to the global Unreal origin. */
|
||||
World,
|
||||
|
||||
/**
|
||||
* Tracking space is relative to the HMD tracking origin.
|
||||
* It does not include the transform of the player pawn.
|
||||
*/
|
||||
Tracking,
|
||||
};
|
||||
@@ -0,0 +1,206 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "OculusXRAnchorComponent.h"
|
||||
#include "OculusXRAnchorTypes.h"
|
||||
#include "OculusXRAnchorsRequests.h"
|
||||
|
||||
DECLARE_DELEGATE_TwoParams(FOculusXRSpatialAnchorCreateDelegate, EOculusXRAnchorResult::Type /*Result*/, UOculusXRAnchorComponent* /*Anchor*/);
|
||||
DECLARE_DELEGATE_TwoParams(FOculusXRAnchorEraseDelegate, EOculusXRAnchorResult::Type /*Result*/, FOculusXRUUID /*AnchorUUID*/);
|
||||
DECLARE_DELEGATE_FourParams(FOculusXRAnchorSetComponentStatusDelegate, EOculusXRAnchorResult::Type /*Result*/, uint64 /*AnchorHandle*/, EOculusXRSpaceComponentType /*ComponentType*/, bool /*Enabled*/);
|
||||
DECLARE_DELEGATE_TwoParams(FOculusXRAnchorSaveDelegate, EOculusXRAnchorResult::Type /*Result*/, UOculusXRAnchorComponent* /*Anchor*/);
|
||||
DECLARE_DELEGATE_TwoParams(FOculusXRAnchorSaveListDelegate, EOculusXRAnchorResult::Type /*Result*/, const TArray<UOculusXRAnchorComponent*>& /*SavedAnchors*/);
|
||||
DECLARE_DELEGATE_TwoParams(FOculusXRAnchorQueryDelegate, EOculusXRAnchorResult::Type /*Result*/, const TArray<FOculusXRSpaceQueryResult>& /*Results*/);
|
||||
DECLARE_DELEGATE_ThreeParams(FOculusXRAnchorShareDelegate, EOculusXRAnchorResult::Type /*Result*/, const TArray<UOculusXRAnchorComponent*>& /*Anchors*/, const TArray<uint64>& /*Users*/);
|
||||
DECLARE_DELEGATE_TwoParams(FOculusXRSaveAnchorsDelegate, EOculusXRAnchorResult::Type /*Result*/, const TArray<UOculusXRAnchorComponent*>& /*SavedAnchors*/);
|
||||
DECLARE_DELEGATE_FourParams(FOculusXREraseAnchorsDelegate, EOculusXRAnchorResult::Type /*Result*/, const TArray<UOculusXRAnchorComponent*>& /*ErasedAnchors*/, const TArray<FOculusXRUInt64>& /*ErasedAnchorsUUIDs*/, const TArray<FOculusXRUUID>& /*ErasedAnchorsUUIDs*/);
|
||||
DECLARE_DELEGATE_OneParam(FOculusXRDiscoverAnchorsResultsDelegate, const TArray<FOculusXRAnchorsDiscoverResult>& /*DiscoveredSpace*/);
|
||||
DECLARE_DELEGATE_OneParam(FOculusXRDiscoverAnchorsCompleteDelegate, EOculusXRAnchorResult::Type /*Result*/);
|
||||
DECLARE_DELEGATE_TwoParams(FOculusXRGetSharedAnchorsDelegate, EOculusXRAnchorResult::Type /*Result*/, const TArray<FOculusXRAnchorsDiscoverResult>& /*Results*/);
|
||||
|
||||
namespace OculusXRAnchors
|
||||
{
|
||||
struct OCULUSXRANCHORS_API FOculusXRAnchors
|
||||
{
|
||||
void Initialize();
|
||||
void Teardown();
|
||||
|
||||
static FOculusXRAnchors* GetInstance();
|
||||
|
||||
static bool CreateSpatialAnchor(const FTransform& InTransform, AActor* TargetActor, const FOculusXRSpatialAnchorCreateDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult);
|
||||
static bool EraseAnchor(UOculusXRAnchorComponent* Anchor, const FOculusXRAnchorEraseDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult);
|
||||
static bool DestroyAnchor(uint64 AnchorHandle, EOculusXRAnchorResult::Type& OutResult);
|
||||
|
||||
static bool SetAnchorComponentStatus(UOculusXRAnchorComponent* Anchor, EOculusXRSpaceComponentType SpaceComponentType, bool Enable, float Timeout, const FOculusXRAnchorSetComponentStatusDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult);
|
||||
static bool GetAnchorComponentStatus(UOculusXRAnchorComponent* Anchor, EOculusXRSpaceComponentType SpaceComponentType, bool& OutEnabled, bool& OutChangePending, EOculusXRAnchorResult::Type& OutResult);
|
||||
static bool GetAnchorSupportedComponents(UOculusXRAnchorComponent* Anchor, TArray<EOculusXRSpaceComponentType>& OutSupportedComponents, EOculusXRAnchorResult::Type& OutResult);
|
||||
|
||||
static bool SetComponentStatus(uint64 Space, EOculusXRSpaceComponentType SpaceComponentType, bool Enable, float Timeout, const FOculusXRAnchorSetComponentStatusDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult);
|
||||
static bool GetComponentStatus(uint64 AnchorHandle, EOculusXRSpaceComponentType SpaceComponentType, bool& OutEnabled, bool& OutChangePending, EOculusXRAnchorResult::Type& OutResult);
|
||||
static bool GetSupportedComponents(uint64 AnchorHandle, TArray<EOculusXRSpaceComponentType>& OutSupportedComponents, EOculusXRAnchorResult::Type& OutResult);
|
||||
|
||||
static bool SaveAnchor(UOculusXRAnchorComponent* Anchor, EOculusXRSpaceStorageLocation StorageLocation, const FOculusXRAnchorSaveDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult);
|
||||
static bool SaveAnchorList(const TArray<UOculusXRAnchorComponent*>& Anchors, EOculusXRSpaceStorageLocation StorageLocation, const FOculusXRAnchorSaveListDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult);
|
||||
|
||||
static bool QueryAnchors(const TArray<FOculusXRUUID>& AnchorUUIDs, EOculusXRSpaceStorageLocation Location, const FOculusXRAnchorQueryDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult);
|
||||
static bool QueryAnchorsAdvanced(const FOculusXRSpaceQueryInfo& QueryInfo, const FOculusXRAnchorQueryDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult);
|
||||
|
||||
static bool ShareAnchors(const TArray<UOculusXRAnchorComponent*>& Anchors, const TArray<uint64>& OculusUserIDs, const FOculusXRAnchorShareDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult);
|
||||
static bool ShareAnchors(const TArray<uint64>& AnchorHandles, const TArray<uint64>& OculusUserIDs, const FOculusXRAnchorShareDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult);
|
||||
|
||||
static bool GetSpaceContainerUUIDs(uint64 Space, TArray<FOculusXRUUID>& OutUUIDs, EOculusXRAnchorResult::Type& OutResult);
|
||||
|
||||
static bool SaveAnchors(const TArray<UOculusXRAnchorComponent*>& Anchors, const FOculusXRSaveAnchorsDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult);
|
||||
static bool EraseAnchors(const TArray<UOculusXRAnchorComponent*>& Anchors, const FOculusXREraseAnchorsDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult);
|
||||
static bool EraseAnchors(const TArray<FOculusXRUInt64>& AnchorHandles, const TArray<FOculusXRUUID>& AnchorUUIDs, const FOculusXREraseAnchorsDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult);
|
||||
static bool DiscoverAnchors(const FOculusXRSpaceDiscoveryInfo& DiscoveryInfo, const FOculusXRDiscoverAnchorsResultsDelegate& DiscoveryResultsCallback, const FOculusXRDiscoverAnchorsCompleteDelegate& DiscoveryCompleteCallback, EOculusXRAnchorResult::Type& OutResult);
|
||||
static bool GetSharedAnchors(const TArray<FOculusXRUUID>& AnchorUUIDs, const FOculusXRGetSharedAnchorsDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult);
|
||||
|
||||
static TSharedPtr<FShareAnchorsWithGroups> ShareAnchorsAsync(const TArray<FOculusXRUInt64>& AnchorHandles, const TArray<FOculusXRUUID>& Groups, const FShareAnchorsWithGroups::FCompleteDelegate& OnComplete);
|
||||
static TSharedPtr<FGetAnchorsSharedWithGroup> GetSharedAnchorsAsync(const FOculusXRUUID& Group, const TArray<FOculusXRUUID>& WantedAnchors, const FGetAnchorsSharedWithGroup::FCompleteDelegate& OnComplete);
|
||||
|
||||
private:
|
||||
struct AnchorQueryBinding;
|
||||
struct GetSharedAnchorsBinding;
|
||||
|
||||
void HandleSpatialAnchorCreateComplete(FOculusXRUInt64 RequestId, EOculusXRAnchorResult::Type Result, FOculusXRUInt64 Space, FOculusXRUUID UUID);
|
||||
void HandleAnchorEraseComplete(FOculusXRUInt64 RequestId, EOculusXRAnchorResult::Type Result, FOculusXRUUID UUID, EOculusXRSpaceStorageLocation Location);
|
||||
|
||||
void HandleSetComponentStatusComplete(FOculusXRUInt64 RequestId, EOculusXRAnchorResult::Type Result, FOculusXRUInt64 Space, FOculusXRUUID UUID, EOculusXRSpaceComponentType ComponentType, bool Enabled);
|
||||
|
||||
void HandleAnchorSaveComplete(FOculusXRUInt64 RequestId, FOculusXRUInt64 Space, bool Success, EOculusXRAnchorResult::Type Result, FOculusXRUUID UUID);
|
||||
void HandleAnchorSaveListComplete(FOculusXRUInt64 RequestId, EOculusXRAnchorResult::Type Result);
|
||||
|
||||
void HandleAnchorQueryResultElement(FOculusXRUInt64 RequestId, FOculusXRUInt64 Space, FOculusXRUUID UUID);
|
||||
void UpdateQuerySpacesBinding(AnchorQueryBinding* Binding, FOculusXRUInt64 RequestId, FOculusXRUInt64 Space, FOculusXRUUID UUID);
|
||||
void UpdateGetSharedAnchorsBinding(GetSharedAnchorsBinding* Binding, FOculusXRUInt64 RequestId, FOculusXRUInt64 Space, FOculusXRUUID UUID);
|
||||
|
||||
void HandleAnchorQueryComplete(FOculusXRUInt64 RequestId, EOculusXRAnchorResult::Type Result);
|
||||
void QuerySpacesComplete(AnchorQueryBinding* Binding, FOculusXRUInt64 RequestId, EOculusXRAnchorResult::Type Result);
|
||||
void GetSharedAnchorsComplete(GetSharedAnchorsBinding* Binding, FOculusXRUInt64 RequestId, EOculusXRAnchorResult::Type Result);
|
||||
|
||||
void HandleAnchorSharingComplete(FOculusXRUInt64 RequestId, EOculusXRAnchorResult::Type Result);
|
||||
|
||||
void HandleAnchorsSaveComplete(FOculusXRUInt64 RequestId, EOculusXRAnchorResult::Type Result);
|
||||
void HandleAnchorsEraseComplete(FOculusXRUInt64 RequestId, EOculusXRAnchorResult::Type Result);
|
||||
void HandleAnchorsEraseByComponentsComplete(FOculusXRUInt64 RequestId, EOculusXRAnchorResult::Type Result);
|
||||
void HandleAnchorsEraseByHandleAndUUIDComplete(FOculusXRUInt64 RequestId, EOculusXRAnchorResult::Type Result);
|
||||
|
||||
void HandleAnchorsDiscoverResults(FOculusXRUInt64 RequestId, const TArray<FOculusXRAnchorsDiscoverResult>& Results);
|
||||
void HandleAnchorsDiscoverComplete(FOculusXRUInt64 RequestId, EOculusXRAnchorResult::Type Result);
|
||||
|
||||
struct EraseAnchorBinding
|
||||
{
|
||||
FOculusXRUInt64 RequestId;
|
||||
FOculusXRAnchorEraseDelegate Binding;
|
||||
TWeakObjectPtr<UOculusXRAnchorComponent> Anchor;
|
||||
};
|
||||
|
||||
struct SetComponentStatusBinding
|
||||
{
|
||||
FOculusXRUInt64 RequestId;
|
||||
FOculusXRAnchorSetComponentStatusDelegate Binding;
|
||||
uint64 AnchorHandle;
|
||||
};
|
||||
|
||||
struct CreateAnchorBinding
|
||||
{
|
||||
FOculusXRUInt64 RequestId;
|
||||
FOculusXRSpatialAnchorCreateDelegate Binding;
|
||||
TWeakObjectPtr<AActor> Actor;
|
||||
};
|
||||
|
||||
struct SaveAnchorBinding
|
||||
{
|
||||
FOculusXRUInt64 RequestId;
|
||||
FOculusXRAnchorSaveDelegate Binding;
|
||||
EOculusXRSpaceStorageLocation Location;
|
||||
TWeakObjectPtr<UOculusXRAnchorComponent> Anchor;
|
||||
};
|
||||
|
||||
struct SaveAnchorListBinding
|
||||
{
|
||||
FOculusXRUInt64 RequestId;
|
||||
FOculusXRAnchorSaveListDelegate Binding;
|
||||
EOculusXRSpaceStorageLocation Location;
|
||||
TArray<TWeakObjectPtr<UOculusXRAnchorComponent>> SavedAnchors;
|
||||
};
|
||||
|
||||
struct AnchorQueryBinding
|
||||
{
|
||||
FOculusXRUInt64 RequestId;
|
||||
FOculusXRAnchorQueryDelegate Binding;
|
||||
EOculusXRSpaceStorageLocation Location;
|
||||
TArray<FOculusXRSpaceQueryResult> Results;
|
||||
};
|
||||
|
||||
struct ShareAnchorsBinding
|
||||
{
|
||||
FOculusXRUInt64 RequestId;
|
||||
FOculusXRAnchorShareDelegate Binding;
|
||||
TArray<TWeakObjectPtr<UOculusXRAnchorComponent>> SharedAnchors;
|
||||
TArray<uint64> OculusUserIds;
|
||||
};
|
||||
|
||||
struct SaveAnchorsBinding
|
||||
{
|
||||
FOculusXRUInt64 RequestId;
|
||||
FOculusXRSaveAnchorsDelegate Binding;
|
||||
TArray<TWeakObjectPtr<UOculusXRAnchorComponent>> SavedAnchors;
|
||||
};
|
||||
|
||||
struct EraseAnchorsBinding
|
||||
{
|
||||
FOculusXRUInt64 RequestId;
|
||||
FOculusXREraseAnchorsDelegate Binding;
|
||||
TArray<TWeakObjectPtr<UOculusXRAnchorComponent>> ErasedAnchors;
|
||||
TArray<FOculusXRUInt64> ErasedAnchorsHandles;
|
||||
TArray<FOculusXRUUID> ErasedAnchorsUUIDs;
|
||||
};
|
||||
|
||||
struct AnchorDiscoveryBinding
|
||||
{
|
||||
FOculusXRUInt64 RequestId;
|
||||
FOculusXRDiscoverAnchorsResultsDelegate ResultBinding;
|
||||
FOculusXRDiscoverAnchorsCompleteDelegate CompleteBinding;
|
||||
};
|
||||
|
||||
struct GetSharedAnchorsBinding
|
||||
{
|
||||
FOculusXRUInt64 RequestId;
|
||||
FOculusXRGetSharedAnchorsDelegate ResultBinding;
|
||||
TArray<FOculusXRAnchorsDiscoverResult> Results;
|
||||
};
|
||||
|
||||
// Delegate bindings
|
||||
TMap<uint64, CreateAnchorBinding> CreateSpatialAnchorBindings;
|
||||
TMap<uint64, EraseAnchorBinding> EraseAnchorBindings;
|
||||
TMap<uint64, SetComponentStatusBinding> SetComponentStatusBindings;
|
||||
TMap<uint64, SaveAnchorBinding> AnchorSaveBindings;
|
||||
TMap<uint64, SaveAnchorListBinding> AnchorSaveListBindings;
|
||||
TMap<uint64, AnchorQueryBinding> AnchorQueryBindings;
|
||||
TMap<uint64, ShareAnchorsBinding> ShareAnchorsBindings;
|
||||
TMap<uint64, SaveAnchorsBinding> SaveAnchorsBindings;
|
||||
TMap<uint64, EraseAnchorsBinding> EraseAnchorsBindings;
|
||||
TMap<uint64, AnchorDiscoveryBinding> AnchorDiscoveryBindings;
|
||||
TMap<uint64, GetSharedAnchorsBinding> GetSharedAnchorsBindings;
|
||||
|
||||
// Delegate handles
|
||||
FDelegateHandle DelegateHandleAnchorCreate;
|
||||
FDelegateHandle DelegateHandleAnchorErase;
|
||||
FDelegateHandle DelegateHandleSetComponentStatus;
|
||||
FDelegateHandle DelegateHandleAnchorSave;
|
||||
FDelegateHandle DelegateHandleAnchorSaveList;
|
||||
FDelegateHandle DelegateHandleQueryResultsBegin;
|
||||
FDelegateHandle DelegateHandleQueryResultElement;
|
||||
FDelegateHandle DelegateHandleQueryComplete;
|
||||
FDelegateHandle DelegateHandleAnchorShare;
|
||||
FDelegateHandle DelegateHandleAnchorsSave;
|
||||
FDelegateHandle DelegateHandleAnchorsErase;
|
||||
FDelegateHandle DelegateHandleAnchorsDiscoverResults;
|
||||
FDelegateHandle DelegateHandleAnchorsDiscoverComplete;
|
||||
};
|
||||
|
||||
} // namespace OculusXRAnchors
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OculusXRAsyncRequestSystem.h"
|
||||
#include "OculusXRAsyncRequest.h"
|
||||
#include "OculusXRAnchorTypes.h"
|
||||
|
||||
namespace OculusXRAnchors
|
||||
{
|
||||
// Share anchors with group
|
||||
struct OCULUSXRANCHORS_API FShareAnchorsWithGroups :
|
||||
OculusXR::FAsyncRequest<FShareAnchorsWithGroups, EOculusXRAnchorResult::Type, TTuple<TArray<FOculusXRUUID>, TArray<FOculusXRUInt64>>>
|
||||
{
|
||||
public:
|
||||
FShareAnchorsWithGroups(const TArray<FOculusXRUUID>& TargetGroups, const TArray<FOculusXRUInt64>& AnchorsToShare);
|
||||
~FShareAnchorsWithGroups();
|
||||
|
||||
const TArray<FOculusXRUUID>& GetGroups() const { return Groups; }
|
||||
const TArray<FOculusXRUInt64>& GetAnchors() const { return Anchors; }
|
||||
|
||||
protected:
|
||||
virtual void OnInitRequest() override;
|
||||
|
||||
private:
|
||||
static void OnShareComplete(FOculusXRUInt64 RequestId, EOculusXRAnchorResult::Type Result);
|
||||
|
||||
TArray<FOculusXRUUID> Groups;
|
||||
TArray<FOculusXRUInt64> Anchors;
|
||||
FDelegateHandle CallbackHandle;
|
||||
};
|
||||
|
||||
// Get shared anchors from group
|
||||
struct OCULUSXRANCHORS_API FGetAnchorsSharedWithGroup :
|
||||
OculusXR::FAsyncRequest<FGetAnchorsSharedWithGroup, EOculusXRAnchorResult::Type, TArray<FOculusXRAnchor>>
|
||||
{
|
||||
public:
|
||||
FGetAnchorsSharedWithGroup(const FOculusXRUUID& TargetGroup, const TArray<FOculusXRUUID>& WantedAnchors = {});
|
||||
~FGetAnchorsSharedWithGroup();
|
||||
|
||||
void OnResultsAvailable(const TArray<FOculusXRAnchor>& Results);
|
||||
const TArray<FOculusXRAnchor>& GetRetrievedAnchors() const { return RetrievedAnchors; }
|
||||
|
||||
protected:
|
||||
virtual void OnInitRequest() override;
|
||||
|
||||
private:
|
||||
static void OnQueryComplete(FOculusXRUInt64 RequestId, EOculusXRAnchorResult::Type Result);
|
||||
static void OnQueryResultAvailable(FOculusXRUInt64 RequestId, FOculusXRUInt64 AnchorHandle, FOculusXRUUID AnchorUuid);
|
||||
|
||||
FOculusXRUUID Group;
|
||||
TArray<FOculusXRUUID> RequestedAnchors;
|
||||
TArray<FOculusXRAnchor> RetrievedAnchors;
|
||||
FDelegateHandle CallbackHandleComplete;
|
||||
FDelegateHandle CallbackHandleResults;
|
||||
};
|
||||
} // namespace OculusXRAnchors
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user