Android build settings + metaxr
This commit is contained in:
53
Plugins/MetaXR/Source/MRUtilityKit/MRUtilityKit.Build.cs
Normal file
53
Plugins/MetaXR/Source/MRUtilityKit/MRUtilityKit.Build.cs
Normal file
@@ -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();
|
||||
};
|
||||
84
Plugins/MetaXR/Source/MRUtilityKit/Private/MRUtilityKit.cpp
Normal file
84
Plugins/MetaXR/Source/MRUtilityKit/Private/MRUtilityKit.cpp
Normal file
@@ -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;
|
||||
}
|
||||
355
Plugins/MetaXR/Source/MRUtilityKit/Private/MRUtilityKitData.cpp
Normal file
355
Plugins/MetaXR/Source/MRUtilityKit/Private/MRUtilityKitData.cpp
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1522
Plugins/MetaXR/Source/MRUtilityKit/Private/MRUtilityKitRoom.cpp
Normal file
1522
Plugins/MetaXR/Source/MRUtilityKit/Private/MRUtilityKitRoom.cpp
Normal file
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);
|
||||
}
|
||||
|
||||
412
Plugins/MetaXR/Source/MRUtilityKit/Public/MRUtilityKit.h
Normal file
412
Plugins/MetaXR/Source/MRUtilityKit/Public/MRUtilityKit.h
Normal file
@@ -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;
|
||||
};
|
||||
281
Plugins/MetaXR/Source/MRUtilityKit/Public/MRUtilityKitAnchor.h
Normal file
281
Plugins/MetaXR/Source/MRUtilityKit/Public/MRUtilityKitAnchor.h
Normal file
@@ -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;
|
||||
};
|
||||
139
Plugins/MetaXR/Source/MRUtilityKit/Public/MRUtilityKitData.h
Normal file
139
Plugins/MetaXR/Source/MRUtilityKit/Public/MRUtilityKitData.h
Normal file
@@ -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);
|
||||
};
|
||||
481
Plugins/MetaXR/Source/MRUtilityKit/Public/MRUtilityKitRoom.h
Normal file
481
Plugins/MetaXR/Source/MRUtilityKit/Public/MRUtilityKitRoom.h
Normal file
@@ -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",
|
||||
});
|
||||
}
|
||||
}
|
||||
87391
Plugins/MetaXR/Source/MRUtilityKitEditor/Private/DistanceMapTestData.h
Normal file
87391
Plugins/MetaXR/Source/MRUtilityKitEditor/Private/DistanceMapTestData.h
Normal file
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();
|
||||
});
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
18
Plugins/MetaXR/Source/MRUtilityKitEditor/Private/MeshActor.h
Normal file
18
Plugins/MetaXR/Source/MRUtilityKitEditor/Private/MeshActor.h
Normal file
@@ -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();
|
||||
};
|
||||
2674
Plugins/MetaXR/Source/MRUtilityKitEditor/Private/TestHelper.cpp
Normal file
2674
Plugins/MetaXR/Source/MRUtilityKitEditor/Private/TestHelper.cpp
Normal file
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;
|
||||
};
|
||||
@@ -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);
|
||||
1119
Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchors.cpp
Normal file
1119
Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchors.cpp
Normal file
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,
|
||||
};
|
||||
206
Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchors.h
Normal file
206
Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchors.h
Normal file
@@ -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