diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini
index c2d7c6c..6ed0593 100644
--- a/Config/DefaultEngine.ini
+++ b/Config/DefaultEngine.ini
@@ -112,7 +112,7 @@ VisualizeCalibrationColorMaterialPath=None
VisualizeCalibrationCustomMaterialPath=None
VisualizeCalibrationGrayscaleMaterialPath=None
r.Mobile.AntiAliasing=3
-r.Mobile.FloatPrecisionMode=2
+r.Mobile.FloatPrecisionMode=0
r.OpenGL.ForceDXC=0
r.DynamicGlobalIlluminationMethod=1
@@ -183,8 +183,8 @@ StoreVersionOffsetArm64=0
StoreVersionOffsetX8664=0
ApplicationDisplayName=
VersionDisplayName=1.0
-MinSDKVersion=23
-TargetSDKVersion=25
+MinSDKVersion=32
+TargetSDKVersion=32
InstallLocation=InternalOnly
bEnableLint=False
bPackageDataInsideApk=True
diff --git a/Plugins/MetaXR/Config/BaseOculusXR.ini b/Plugins/MetaXR/Config/BaseOculusXR.ini
new file mode 100644
index 0000000..cd0a0ca
--- /dev/null
+++ b/Plugins/MetaXR/Config/BaseOculusXR.ini
@@ -0,0 +1,140 @@
+[CoreRedirects]
+;
++PackageRedirects=(OldName="/OculusVR/",NewName="/OculusXR/",MatchSubstring=true)
+;
++ClassRedirects=(OldName="/Script/OculusHMD.OculusResourceHolder", NewName="/Script/OculusXRHMD.OculusXRResourceHolder")
++ClassRedirects=(OldName="/Script/OculusHMD.OculusPassthroughLayerComponent", NewName="/Script/OculusXRPassthrough.OculusXRPassthroughLayerComponent")
++ClassRedirects=(OldName="/Script/OculusHMD.StereoLayerShapeUserDefined", NewName="/Script/OculusXRPassthrough.OculusXRStereoLayerShapeUserDefined")
++ClassRedirects=(OldName="/Script/OculusHMD.StereoLayerShapeReconstructed", NewName="/Script/OculusXRPassthrough.OculusXRStereoLayerShapeReconstructed")
++ClassRedirects=(OldName="/Script/OculusHMD.OculusHMDRuntimeSettings", NewName="/Script/OculusXRHMD.OculusXRHMDRuntimeSettings")
++ClassRedirects=(OldName="/Script/OculusHMD.OculusEventComponent", NewName="/Script/OculusXRHMD.OculusXREventComponent")
++ClassRedirects=(OldName="/Script/OculusHMD.OculusSceneCaptureCubemap", NewName="/Script/OculusXRHMD.OculusXRSceneCaptureCubemap")
++ClassRedirects=(OldName="/Script/OculusHMD.PassthroughLayerBase", NewName="/Script/OculusXRPassthrough.OculusXRPassthroughLayerBase")
++ClassRedirects=(OldName="/Script/OculusHMD.OculusFunctionLibrary", NewName="/Script/OculusXRHMD.OculusXRFunctionLibrary")
+;
++EnumRedirects=(OldName="EOculusXrApi", NewName="/Script/OculusXRHMD.EOculusXRXrApi")
++EnumRedirects=(OldName="EHandTrackingSupport", NewName="/Script/OculusXRHMD.EOculusXRHandTrackingSupport")
++EnumRedirects=(OldName="ETrackedDeviceType", NewName="/Script/OculusXRHMD.EOculusXRTrackedDeviceType")
++EnumRedirects=(OldName="EHandTrackingFrequency", NewName="/Script/OculusXRHMD.EOculusXRHandTrackingFrequency")
++EnumRedirects=(OldName="EColorMapType", NewName="/Script/OculusXRHMD.EOculusXRColorMapType")
++EnumRedirects=(OldName="EPassthroughLayerOrder", NewName="/Script/OculusXRHMD.EOculusXRPassthroughLayerOrder")
++EnumRedirects=(OldName="EOculusDeviceType", NewName="/Script/OculusXRHMD.EOculusXRDeviceType")
++EnumRedirects=(OldName="EColorSpace", NewName="/Script/OculusXRHMD.EOculusXRColorSpace")
++EnumRedirects=(OldName="EBoundaryType", NewName="/Script/OculusXRHMD.EOculusXRBoundaryType")
++EnumRedirects=(OldName="EProcessorPerformanceLevel", NewName="/Script/OculusXRHMD.EOculusXRProcessorPerformanceLevel")
+;
++StructRedirects=(OldName="/Script/OculusHMD.GuardianTestResult", NewName="/Script/OculusXRHMD.OculusXRGuardianTestResult")
++StructRedirects=(OldName="/Script/OculusHMD.OculusSplashDesc", NewName="/Script/OculusXRHMD.OculusXRSplashDesc")
++StructRedirects=(OldName="/Script/OculusHMD.HmdUserProfile", NewName="/Script/OculusXRHMD.OculusXRHmdUserProfile")
++StructRedirects=(OldName="/Script/OculusHMD.HmdUserProfileField", NewName="/Script/OculusXRHMD.OculusXRHmdUserProfileField")
+;
++ClassRedirects=(OldName="/Script/OculusInput.OculusHandComponent", NewName="/Script/OculusXRInput.OculusXRHandComponent")
++ClassRedirects=(OldName="/Script/OculusInput.OculusMRFunctionLibrary", NewName="/Script/OculusXRInput.OculusXRMRFunctionLibrary")
++ClassRedirects=(OldName="/Script/OculusInput.OculusInputFunctionLibrary", NewName="/Script/OculusXRInput.OculusXRInputFunctionLibrary")
+;
++EnumRedirects=(OldName="ETrackingConfidence", NewName="/Script/OculusXRInput.EOculusXRTrackingConfidence")
++EnumRedirects=(OldName="EConfidenceBehavior", NewName="/Script/OculusXRInput.EOculusXRConfidenceBehavior")
++EnumRedirects=(OldName="EOculusHandType", NewName="/Script/OculusXRInput.EOculusXRHandType")
++EnumRedirects=(OldName="EOculusFinger", NewName="/Script/OculusXRInput.EOculusXRFinger")
++EnumRedirects=(OldName="ESystemGestureBehavior", NewName="/Script/OculusXRInput.EOculusXRSystemGestureBehavior")
++EnumRedirects=(OldName="EBone", NewName="/Script/OculusXRInput.EOculusXRBone")
+;
++StructRedirects=(OldName="/Script/OculusInput.OculusCapsuleCollider", NewName="/Script/OculusXRInput.OculusXRCapsuleCollider")
+;
+;
+;
+;
++ClassRedirects=(OldName="/Script/OculusEditor.OculusHMDRuntimeSettings", NewName="/Script/OculusXREditor.OculusXRHMDRuntimeSettings")
++ClassRedirects=(OldName="/Script/OculusEditor.OculusEditorSettings", NewName="/Script/OculusXREditor.OculusXREditorSettings")
++ClassRedirects=(OldName="/Script/OculusEditor.OculusPlatformToolSettings", NewName="/Script/OculusXREditor.OculusXRPlatformToolSettings")
+;
++EnumRedirects=(OldName="EOculusAssetType", NewName="/Script/OculusXREditor.EOculusXRAssetType")
++EnumRedirects=(OldName="EOculusPlatform", NewName="/Script/OculusXREditor.EOculusXRPlatform")
++EnumRedirects=(OldName="EOculusGamepadEmulation", NewName="/Script/OculusXREditor.EOculusXRGamepadEmulation")
+;
++StructRedirects=(OldName="/Script/OculusEditor.RedistPackage", NewName="/Script/OculusXREditor.OculusXRRedistPackage")
++StructRedirects=(OldName="/Script/OculusEditor.AssetConfig", NewName="/Script/OculusXREditor.OculusXRAssetConfig")
+;
++ClassRedirects=(OldName="/Script/OculusMR.OculusFunctionLibrary", NewName="/Script/OculusMR.OculusXRFunctionLibrary")
++ClassRedirects=(OldName="/Script/OculusMR.OculusMR_Settings", NewName="/Script/OculusMR.OculusXRMR_Settings")
++ClassRedirects=(OldName="/Script/OculusMR.OculusMRFunctionLibrary", NewName="/Script/OculusMR.OculusXRMRFunctionLibrary")
+;
++EnumRedirects=(OldName="EOculusMR_CameraDeviceEnum", NewName="/Script/OculusMR.EOculusXRMR_CameraDeviceEnum")
++EnumRedirects=(OldName="EOculusMR_PostProcessEffects", NewName="/Script/OculusMR.EOculusXRMR_PostProcessEffects")
++EnumRedirects=(OldName="EOculusMR_CompositionMethod", NewName="/Script/OculusMR.EOculusXRMR_CompositionMethod")
++EnumRedirects=(OldName="EOculusMR_ClippingReference", NewName="/Script/OculusMR.EOculusXRMR_ClippingReference")
+;
++StructRedirects=(OldName="/Script/OculusMR.OculusMR_PlaneMeshTriangle", NewName="/Script/OculusMR.OculusXRMR_PlaneMeshTriangle")
++StructRedirects=(OldName="/Script/OculusMR.TrackedCamera", NewName="/Script/OculusMR.OculusXRTrackedCamera")
+;
++EnumRedirects=(OldName="EOculusXRXrApi",ValueChanges=(("LegacyOVRPlugin","OVRPluginOpenXR")))
+;
++EnumRedirects=(OldName="ETiledMultiResLevel",NewName="EOculusXRFoveatedRenderingLevel",ValueChanges=(("ETiledMultiResLevel_Off","Off"),("ETiledMultiResLevel_LMSLow","Low"),("ETiledMultiResLevel_LMSMedium","Medium"),("ETiledMultiResLevel_LMSHigh","High"),("ETiledMultiResLevel_LMSHighTop","HighTop")))
++FunctionRedirects=(OldName="GetTiledMultiresLevel",NewName="GetFoveatedRenderingLevel")
++FunctionRedirects=(OldName="SetTiledMultiresLevel",NewName="SetFoveatedRenderingLevel")
++EnumRedirects=(OldName="EFixedFoveatedRenderingLevel",NewName="EOculusXRFoveatedRenderingLevel",ValueChanges=(("FFR_Off","Off"),("FFR_Low","Low"),("FFR_Medium","Medium"),("FFR_High","High"),("FFR_HighTop","HighTop")))
++FunctionRedirects=(OldName="GetFixedFoveatedRenderingLevel",NewName="GetFoveatedRenderingLevel")
++FunctionRedirects=(OldName="SetFixedFoveatedRenderingLevel",NewName="SetFoveatedRenderingLevel")
+;
++EnumRedirects=(OldName="/Script/OculusXRHMD.EOculusDeviceType",ValueChanges=(("OculusQuest","OculusQuest_Deprecated"),("Quest_Link","Quest_Link_Deprecated")))
+;
+; Anchors and Scene redirects
++StructRedirects=(OldName="/Script/OculusAnchors.OculusSpaceQueryInfo", NewName="/Script/OculusXRAnchors.OculusXRSpaceQueryInfo")
++StructRedirects=(OldName="/Script/OculusAnchors.OculusSpaceQueryResult", NewName="/Script/OculusXRAnchors.OculusXRSpaceQueryResult")
++StructRedirects=(OldName="/Script/OculusAnchors.OculusSpaceQueryFilterValues", NewName="/Script/OculusXRAnchors.OculusXRSpaceQueryFilterValues")
++StructRedirects=(OldName="/Script/OculusAnchors.OculusAnchorManager", NewName="/Script/OculusXRAnchors.OculusXRAnchorManager")
++StructRedirects=(OldName="/Script/OculusAnchors.OculusSpatialAnchorManager", NewName="/Script/OculusXRAnchors.OculusXRSpatialAnchorManager")
++StructRedirects=(OldName="/Script/OculusAnchors.OculusRoomLayoutManager", NewName="/Script/OculusXRAnchors.OculusXRRoomLayoutManager")
++StructRedirects=(OldName="/Script/OculusAnchors.OculusAnchors", NewName="/Script/OculusXRAnchors.OculusXRAnchors")
++StructRedirects=(OldName="/Script/OculusAnchors.OculusRoomLayout", NewName="/Script/OculusXRAnchors.OculusXRRoomLayout")
++StructRedirects=(OldName="/Script/OculusScene.OculusSpawnedSceneAnchorProperties", NewName="/Script/OculusXRScene.OculusXRSpawnedSceneAnchorProperties")
++StructRedirects=(OldName="/Script/OculusAnchors.UUID", NewName="/Script/OculusXRAnchors.OculusXRUUID")
++StructRedirects=(OldName="/Script/OculusAnchors.UInt64", NewName="/Script/OculusXRAnchors.OculusXRUInt64")
+;
++EnumRedirects=(OldName="EOculusSpaceQueryFilterType", NewName="/Script/OculusXRAnchors.EOculusXRSpaceQueryFilterType")
++EnumRedirects=(OldName="EOculusSpaceStorageLocation", NewName="/Script/OculusXRAnchors.EOculusXRSpaceStorageLocation")
++EnumRedirects=(OldName="EOculusSpaceStoragePersistenceMode", NewName="/Script/OculusXRAnchors.EOculusXRSpaceStoragePersistenceMode")
++EnumRedirects=(OldName="EOculusSpaceComponentType", NewName="/Script/OculusXRAnchors.EOculusXRSpaceComponentType")
++EnumRedirects=(OldName="EOculusLaunchCaptureFlowWhenMissingScene", NewName="/Script/OculusXRScene.EOculusXRLaunchCaptureFlowWhenMissingScene")
+;
++ClassRedirects=(OldName="/Script/OculusAnchors.OculusAnchorComponent", NewName="/Script/OculusXRAnchors.OculusXRAnchorComponent")
++ClassRedirects=(OldName="/Script/OculusAnchors.OculusAnchorBPFuctionLibrary", NewName="/Script/OculusXRAnchors.OculusXRAnchorBPFuctionLibrary")
++ClassRedirects=(OldName="/Script/OculusAnchors.OculusAsyncAction_CreateSpatialAnchor", NewName="/Script/OculusXRAnchors.OculusXRAsyncAction_CreateSpatialAnchor")
++ClassRedirects=(OldName="/Script/OculusAnchors.OculusAsyncAction_EraseAnchor", NewName="/Script/OculusXRAnchors.OculusXRAsyncAction_EraseAnchor")
++ClassRedirects=(OldName="/Script/OculusAnchors.OculusAsyncAction_SaveAnchor", NewName="/Script/OculusXRAnchors.OculusXRAsyncAction_SaveAnchor")
++ClassRedirects=(OldName="/Script/OculusAnchors.OculusAsyncAction_QueryAnchors", NewName="/Script/OculusXRAnchors.OculusXRAsyncAction_QueryAnchors")
++ClassRedirects=(OldName="/Script/OculusAnchors.OculusAsyncAction_SetAnchorComponentStatus", NewName="/Script/OculusXRAnchors.OculusXRAsyncAction_SetAnchorComponentStatus")
++ClassRedirects=(OldName="/Script/OculusAnchors.OculusRoomLayoutManagerComponent", NewName="/Script/OculusXRAnchors.OculusXRRoomLayoutManagerComponent")
++ClassRedirects=(OldName="/Script/OculusAnchors.OculusSpatialAnchorComponent", NewName="/Script/OculusXRAnchors.OculusXRSpatialAnchorComponent")
++ClassRedirects=(OldName="/Script/OculusScene.OculusSceneAnchorComponent", NewName="/Script/OculusXRScene.OculusXRSceneAnchorComponent")
++ClassRedirects=(OldName="/Script/OculusScene.OculusSceneActor", NewName="/Script/OculusXRScene.OculusXRSceneActor")
+;
++FunctionRedirects=(OldName="OculusAsyncAction_CreateSpatialAnchor.OculusAsyncCreateSpatialAnchor",NewName="OculusXRAsyncAction_CreateSpatialAnchor.OculusXRAsyncCreateSpatialAnchor")
++FunctionRedirects=(OldName="OculusAsyncAction_EraseAnchor.OculusAsyncEraseAnchor",NewName="OculusXRAsyncAction_EraseAnchor.OculusXRAsyncEraseAnchor")
++FunctionRedirects=(OldName="OculusAsyncAction_SaveAnchor.OculusAsyncSaveAnchor",NewName="OculusXRAsyncAction_SaveAnchor.OculusXRAsyncSaveAnchor")
++FunctionRedirects=(OldName="OculusAsyncAction_QueryAnchors.OculusAsyncQueryAnchors",NewName="OculusXRAsyncAction_QueryAnchors.OculusXRAsyncQueryAnchors")
++FunctionRedirects=(OldName="OculusAsyncAction_QueryAnchors.OculusAsyncQueryAnchorsAdvanced",NewName="OculusXRAsyncAction_QueryAnchors.OculusXRAsyncQueryAnchorsAdvanced")
++FunctionRedirects=(OldName="OculusAsyncAction_SetAnchorComponentStatus.OculusAsyncSetAnchorComponentStatus",NewName="OculusXRAsyncAction_SetAnchorComponentStatus.OculusXRAsyncSetAnchorComponentStatus")
+;
++ClassRedirects=(OldName="/Script/OculusXRHMD.OculusXRPassthroughLayerComponent", NewName="/Script/OculusXRPassthrough.OculusXRPassthroughLayerComponent")
++ClassRedirects=(OldName="/Script/OculusXRHMD.OculusXRPassthroughLayerBase", NewName="/Script/OculusXRPassthrough.OculusXRPassthroughLayerBase")
++ClassRedirects=(OldName="/Script/OculusXRHMD.OculusXRStereoLayerShapeUserDefined", NewName="/Script/OculusXRPassthrough.OculusXRStereoLayerShapeUserDefined")
++ClassRedirects=(OldName="/Script/OculusXRHMD.OculusXRStereoLayerShapeReconstructed", NewName="/Script/OculusXRPassthrough.OculusXRStereoLayerShapeReconstructed")
+
+; Movement
++EnumRedirects=(OldName="EOculusBodyTrackingMode", NewName="/Script/OculusXRMovement.EOculusXRBodyTrackingMode")
++EnumRedirects=(OldName="EOculusBoneID", NewName="/Script/OculusXRMovement.EOculusXRBoneID")
++EnumRedirects=(OldName="EOculusFaceExpression", NewName="/Script/OculusXRMovement.EOculusXRFaceExpression")
++EnumRedirects=(OldName="EOculusFaceConfidence", NewName="/Script/OculusXRMovement.EOculusXRFaceConfidence")
++EnumRedirects=(OldName="EOculusEye", NewName="/Script/OculusXRMovement.EOculusXREye")
+;
++StructRedirects=(OldName="/Script/OculusMovement.OculusBodyJoint", NewName="/Script/OculusXRMovement.OculusXRBodyJoint")
++StructRedirects=(OldName="/Script/OculusMovement.OculusBodyState", NewName="/Script/OculusXRMovement.OculusXRBodyState")
++StructRedirects=(OldName="/Script/OculusMovement.OculusFaceState", NewName="/Script/OculusXRMovement.OculusXRFaceState")
++StructRedirects=(OldName="/Script/OculusMovement.OculusEyeGazeState", NewName="/Script/OculusXRMovement.OculusXREyeGazeState")
++StructRedirects=(OldName="/Script/OculusMovement.OculusEyeGazesState", NewName="/Script/OculusXRMovement.OculusXREyeGazesState")
+;
++ClassRedirects=(OldName="/Script/OculusMovement.OculusBodyTrackingComponent", NewName="/Script/OculusXRMovement.OculusXRBodyTrackingComponent")
++ClassRedirects=(OldName="/Script/OculusMovement.OculusEyeTrackingComponent", NewName="/Script/OculusXRMovement.OculusXREyeTrackingComponent")
++ClassRedirects=(OldName="/Script/OculusMovement.OculusFaceTrackingComponent", NewName="/Script/OculusXRMovement.OculusXRFaceTrackingComponent")
++ClassRedirects=(OldName="/Script/OculusMovement.OculusMovementFunctionLibrary", NewName="/Script/OculusXRMovement.OculusXRMovementFunctionLibrary")
diff --git a/Plugins/MetaXR/Config/FilterPlugin.ini b/Plugins/MetaXR/Config/FilterPlugin.ini
new file mode 100644
index 0000000..9d21933
--- /dev/null
+++ b/Plugins/MetaXR/Config/FilterPlugin.ini
@@ -0,0 +1,14 @@
+[FilterPlugin]
+; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and
+; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively.
+;
+; Examples:
+; /README.txt
+; /Extras/...
+; /Binaries/ThirdParty/*.dll
+/Config/...
+/build.log
+/Shaders/...
+/Source/Thirdparty/...
+
+
diff --git a/Plugins/MetaXR/Content/Materials/LeftMetaQuestTouchPlusMaterial.uasset b/Plugins/MetaXR/Content/Materials/LeftMetaQuestTouchPlusMaterial.uasset
new file mode 100644
index 0000000..91f2538
--- /dev/null
+++ b/Plugins/MetaXR/Content/Materials/LeftMetaQuestTouchPlusMaterial.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:18b4cd5b341fdf48d7f3011f2432cbc4c92e12a8dc1ff927b38644752eb36ecb
+size 9496
diff --git a/Plugins/MetaXR/Content/Materials/LeftMetaQuestTouchProBatteryIndicatorMaterial.uasset b/Plugins/MetaXR/Content/Materials/LeftMetaQuestTouchProBatteryIndicatorMaterial.uasset
new file mode 100644
index 0000000..a0c16d7
--- /dev/null
+++ b/Plugins/MetaXR/Content/Materials/LeftMetaQuestTouchProBatteryIndicatorMaterial.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a61acc9ffab64e3b5a61c33e7956b1d6120f9a4ff04478e5c953d2b993c91383
+size 10304
diff --git a/Plugins/MetaXR/Content/Materials/LeftMetaQuestTouchProMaterial.uasset b/Plugins/MetaXR/Content/Materials/LeftMetaQuestTouchProMaterial.uasset
new file mode 100644
index 0000000..ecfb794
--- /dev/null
+++ b/Plugins/MetaXR/Content/Materials/LeftMetaQuestTouchProMaterial.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a7b09956cc5b47dc73e3d3df164c6729aa60a4c40907d4fc890545d691738f68
+size 10447
diff --git a/Plugins/MetaXR/Content/Materials/MetaQuestTouchProBatteryIndicatorMaterial.uasset b/Plugins/MetaXR/Content/Materials/MetaQuestTouchProBatteryIndicatorMaterial.uasset
new file mode 100644
index 0000000..480b235
--- /dev/null
+++ b/Plugins/MetaXR/Content/Materials/MetaQuestTouchProBatteryIndicatorMaterial.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:66ea7aed173476c8fffcd897f6ad05578afb0fb4a1c8905fddf8f84a19c3051a
+size 86538
diff --git a/Plugins/MetaXR/Content/Materials/MetaQuestTouchProMaterial.uasset b/Plugins/MetaXR/Content/Materials/MetaQuestTouchProMaterial.uasset
new file mode 100644
index 0000000..653c1dd
--- /dev/null
+++ b/Plugins/MetaXR/Content/Materials/MetaQuestTouchProMaterial.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ca52344d9f210e3765146e18b566381198605894dcf484bdbaa4e3f198012bc3
+size 83175
diff --git a/Plugins/MetaXR/Content/Materials/OculusMR_ChromaKey.uasset b/Plugins/MetaXR/Content/Materials/OculusMR_ChromaKey.uasset
new file mode 100644
index 0000000..8360b1c
--- /dev/null
+++ b/Plugins/MetaXR/Content/Materials/OculusMR_ChromaKey.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a08c44e3ac881924182cf152ef04768fa4673124b9fde9dfd3657d687d5fd527
+size 167711
diff --git a/Plugins/MetaXR/Content/Materials/OculusMR_OpaqueColoredMaterial.uasset b/Plugins/MetaXR/Content/Materials/OculusMR_OpaqueColoredMaterial.uasset
new file mode 100644
index 0000000..310103e
--- /dev/null
+++ b/Plugins/MetaXR/Content/Materials/OculusMR_OpaqueColoredMaterial.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:56e4bb5c2b9e5d0765e0fbd36be331f9c2ab0dbc260cc83cf6e97a4d019834eb
+size 94122
diff --git a/Plugins/MetaXR/Content/Materials/OculusMR_WhiteMaterial.uasset b/Plugins/MetaXR/Content/Materials/OculusMR_WhiteMaterial.uasset
new file mode 100644
index 0000000..501e4c8
--- /dev/null
+++ b/Plugins/MetaXR/Content/Materials/OculusMR_WhiteMaterial.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5c3c46ffd291896349c33c643f6a487b9d1dd7397928d31e8e35733af34fbc83
+size 59981
diff --git a/Plugins/MetaXR/Content/Materials/PokeAHoleMaterial.uasset b/Plugins/MetaXR/Content/Materials/PokeAHoleMaterial.uasset
new file mode 100644
index 0000000..5862022
--- /dev/null
+++ b/Plugins/MetaXR/Content/Materials/PokeAHoleMaterial.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:34b2ede22fa887498b332b44dde4179f376820a3047ae37f7128efa51d79ba95
+size 6499
diff --git a/Plugins/MetaXR/Content/Materials/RightMetaQuestTouchPlusMaterial.uasset b/Plugins/MetaXR/Content/Materials/RightMetaQuestTouchPlusMaterial.uasset
new file mode 100644
index 0000000..58d51ea
--- /dev/null
+++ b/Plugins/MetaXR/Content/Materials/RightMetaQuestTouchPlusMaterial.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8533a56d45e68757df582780a1b53fef2a67dab954eeecf1407bcf2f6c1f0139
+size 9368
diff --git a/Plugins/MetaXR/Content/Materials/RightMetaQuestTouchProBatteryIndicatorMaterial.uasset b/Plugins/MetaXR/Content/Materials/RightMetaQuestTouchProBatteryIndicatorMaterial.uasset
new file mode 100644
index 0000000..d063a10
--- /dev/null
+++ b/Plugins/MetaXR/Content/Materials/RightMetaQuestTouchProBatteryIndicatorMaterial.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:79ee74d05bf719de83532ac298cba55d07246aae025582d7596ccf3304865948
+size 10312
diff --git a/Plugins/MetaXR/Content/Materials/RightMetaQuestTouchProMaterial.uasset b/Plugins/MetaXR/Content/Materials/RightMetaQuestTouchProMaterial.uasset
new file mode 100644
index 0000000..595f768
--- /dev/null
+++ b/Plugins/MetaXR/Content/Materials/RightMetaQuestTouchProMaterial.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a2235389523b132b61a9a3181c0da746dc6602bb8a5734d61c9dd50ec3fd6066
+size 9918
diff --git a/Plugins/MetaXR/Content/Materials/TouchForQuest2Material.uasset b/Plugins/MetaXR/Content/Materials/TouchForQuest2Material.uasset
new file mode 100644
index 0000000..c36c3ba
--- /dev/null
+++ b/Plugins/MetaXR/Content/Materials/TouchForQuest2Material.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f7c6b15d5326132ba49af186ca77694380307cb4ada1d4c16e1bafaec77454a7
+size 10829
diff --git a/Plugins/MetaXR/Content/Materials/TouchForQuestRiftSControllerMaterial.uasset b/Plugins/MetaXR/Content/Materials/TouchForQuestRiftSControllerMaterial.uasset
new file mode 100644
index 0000000..1958b33
--- /dev/null
+++ b/Plugins/MetaXR/Content/Materials/TouchForQuestRiftSControllerMaterial.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:36520d74b4b291d8687afbb60a418e64e7f51652976f3b94d7fcb11f7da6b44f
+size 99699
diff --git a/Plugins/MetaXR/Content/Materials/model_pieces_controllerMATphongRT.uasset b/Plugins/MetaXR/Content/Materials/model_pieces_controllerMATphongRT.uasset
new file mode 100644
index 0000000..a089fb0
--- /dev/null
+++ b/Plugins/MetaXR/Content/Materials/model_pieces_controllerMATphongRT.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:577e3f7b7075af3fd27cdc28634a4f53d99c54075dd7f989732d94037906a826
+size 111033
diff --git a/Plugins/MetaXR/Content/Materials/touchController_mat.uasset b/Plugins/MetaXR/Content/Materials/touchController_mat.uasset
new file mode 100644
index 0000000..0e14920
--- /dev/null
+++ b/Plugins/MetaXR/Content/Materials/touchController_mat.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3e824230bd07de2ed74be6b8ddbd5f494c548926f4e3c3c46c0e8bcbb663c2e7
+size 95047
diff --git a/Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchPlus.fbx b/Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchPlus.fbx
new file mode 100644
index 0000000..fabe4d7
--- /dev/null
+++ b/Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchPlus.fbx
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4f62b338735c8087992c208663a0dcb6ae8c95928c1edea3c44f8b37d04da04f
+size 287792
diff --git a/Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchPlus.uasset b/Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchPlus.uasset
new file mode 100644
index 0000000..38c6e6c
--- /dev/null
+++ b/Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchPlus.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fbbe2060876c117412fbe59652c58ded6d9c6dd394e69db67c322eb13729af10
+size 222375
diff --git a/Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchPlusBatteryIndicatorQuad.uasset b/Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchPlusBatteryIndicatorQuad.uasset
new file mode 100644
index 0000000..a7e75e1
--- /dev/null
+++ b/Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchPlusBatteryIndicatorQuad.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e39c18f4f4084cc7586d0c1277aafa45ee1e8beb89bc2cffb7d48726b0ccb4b5
+size 16434
diff --git a/Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchPro.uasset b/Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchPro.uasset
new file mode 100644
index 0000000..c7d211a
--- /dev/null
+++ b/Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchPro.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:df4df64c2debebbaee15e9bd593b9be243d7f4734d4fabba30d28d06ca733d0d
+size 167905
diff --git a/Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchProBatteryIndicatorQuad.uasset b/Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchProBatteryIndicatorQuad.uasset
new file mode 100644
index 0000000..a772841
--- /dev/null
+++ b/Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchProBatteryIndicatorQuad.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0f5cff83f8dc2d0684fb65789bea65e341a3e984a784b8ee48e3b14d320416e0
+size 15403
diff --git a/Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchProNub.uasset b/Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchProNub.uasset
new file mode 100644
index 0000000..d6eb184
--- /dev/null
+++ b/Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchProNub.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d9d27399ac32afd39edb67cdcc20a4068eb95d05abb396532b6112ac6fdcc5c8
+size 26968
diff --git a/Plugins/MetaXR/Content/Meshes/LeftTouchForQuest2.uasset b/Plugins/MetaXR/Content/Meshes/LeftTouchForQuest2.uasset
new file mode 100644
index 0000000..35b8960
--- /dev/null
+++ b/Plugins/MetaXR/Content/Meshes/LeftTouchForQuest2.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9cadbc5a391b529d73b94e66c99b8a51009a10f263a753718a0498c14a95b9f9
+size 1620506
diff --git a/Plugins/MetaXR/Content/Meshes/LeftTouchForQuestRiftSController.fbx b/Plugins/MetaXR/Content/Meshes/LeftTouchForQuestRiftSController.fbx
new file mode 100644
index 0000000..2e4e5b0
--- /dev/null
+++ b/Plugins/MetaXR/Content/Meshes/LeftTouchForQuestRiftSController.fbx
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:496844537b5ab18ffac4466e4197eee9b021abebefb093cf704d3e0a84f25eda
+size 422976
diff --git a/Plugins/MetaXR/Content/Meshes/LeftTouchForQuestRiftSController.uasset b/Plugins/MetaXR/Content/Meshes/LeftTouchForQuestRiftSController.uasset
new file mode 100644
index 0000000..e4ec152
--- /dev/null
+++ b/Plugins/MetaXR/Content/Meshes/LeftTouchForQuestRiftSController.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c9e972e4a04cc074a073c24a3466bd836af6607017c20655cd8ab925a0611887
+size 334980
diff --git a/Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchPlus.fbx b/Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchPlus.fbx
new file mode 100644
index 0000000..8ec73d9
--- /dev/null
+++ b/Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchPlus.fbx
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3ba211be52ccf6e3e8708ec51777b250ce9ca9d2070e1295fc68231020769558
+size 288704
diff --git a/Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchPlus.uasset b/Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchPlus.uasset
new file mode 100644
index 0000000..e0c1c7c
--- /dev/null
+++ b/Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchPlus.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8bd963c95a8d0c150ec7ad42f95230f4aa8e98f0845c5ee3d2fc743757953ba6
+size 221786
diff --git a/Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchPlusBatteryIndicatorQuad.uasset b/Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchPlusBatteryIndicatorQuad.uasset
new file mode 100644
index 0000000..80f8b0d
--- /dev/null
+++ b/Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchPlusBatteryIndicatorQuad.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:663d20fa26629279e88dbcff73b0c853229980c2f65fdd619ea5e917c392b796
+size 15627
diff --git a/Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchPro.uasset b/Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchPro.uasset
new file mode 100644
index 0000000..877b4b5
--- /dev/null
+++ b/Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchPro.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9984f8f285005921951e276bb61066cfd147afbd88543ec5c0d3f1b0d1e3d0cc
+size 168472
diff --git a/Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchProBatteryIndicatorQuad.uasset b/Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchProBatteryIndicatorQuad.uasset
new file mode 100644
index 0000000..afa906a
--- /dev/null
+++ b/Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchProBatteryIndicatorQuad.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ce6e651a9055315c0bb53b161ef6cced4537c850bd2319341dd453f1f3390b9c
+size 15954
diff --git a/Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchProNub.uasset b/Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchProNub.uasset
new file mode 100644
index 0000000..228aab7
--- /dev/null
+++ b/Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchProNub.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6736591f01a4bedf1aefecb29d6cd50a584c37f8ebdbfdb080fec9b6a0349921
+size 27434
diff --git a/Plugins/MetaXR/Content/Meshes/RightTouchForQuest2.uasset b/Plugins/MetaXR/Content/Meshes/RightTouchForQuest2.uasset
new file mode 100644
index 0000000..540f381
--- /dev/null
+++ b/Plugins/MetaXR/Content/Meshes/RightTouchForQuest2.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cdf3cf3027f6b6e5665b2a8b8aa3d36d5cf6e8375ad457fd41eddf45864f4bef
+size 1622442
diff --git a/Plugins/MetaXR/Content/Meshes/RightTouchForQuestRiftSController.fbx b/Plugins/MetaXR/Content/Meshes/RightTouchForQuestRiftSController.fbx
new file mode 100644
index 0000000..ea282ce
--- /dev/null
+++ b/Plugins/MetaXR/Content/Meshes/RightTouchForQuestRiftSController.fbx
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1da951971570105cff39f7f10122caff22e0655811d9be13730ed2f8de305483
+size 422784
diff --git a/Plugins/MetaXR/Content/Meshes/RightTouchForQuestRiftSController.uasset b/Plugins/MetaXR/Content/Meshes/RightTouchForQuestRiftSController.uasset
new file mode 100644
index 0000000..d7b703d
--- /dev/null
+++ b/Plugins/MetaXR/Content/Meshes/RightTouchForQuestRiftSController.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:816ffcda1ebd0c01fc413d37497888df005782ff14f5a603a049f85a9776298b
+size 337188
diff --git a/Plugins/MetaXR/Content/MetaXRControllers.uasset b/Plugins/MetaXR/Content/MetaXRControllers.uasset
new file mode 100644
index 0000000..7a67a0f
--- /dev/null
+++ b/Plugins/MetaXR/Content/MetaXRControllers.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2dfb96c7159d3366f59fcbe277db3086b8e86a04e253748a5f376b85ee95112a
+size 19999
diff --git a/Plugins/MetaXR/Content/OculusMR_GreenKey.uasset b/Plugins/MetaXR/Content/OculusMR_GreenKey.uasset
new file mode 100644
index 0000000..f85346e
--- /dev/null
+++ b/Plugins/MetaXR/Content/OculusMR_GreenKey.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d507f10c27952b84435a1fc313452ae514d1f5467f44774ddf64e2c443d34341
+size 109846
diff --git a/Plugins/MetaXR/Content/OculusModels.tps b/Plugins/MetaXR/Content/OculusModels.tps
new file mode 100644
index 0000000..0fac4c5
--- /dev/null
+++ b/Plugins/MetaXR/Content/OculusModels.tps
@@ -0,0 +1,13 @@
+
+
+ Oculus Models
+ /Engine/Plugins/Runtime/Oculus/OculusVR/Content/
+ Provide our licensees with models that match actual physical Oculus devices.
+ https://developer.oculus.com/licenses/art-1.0/
+
+ Licensees
+ Git
+ P4
+
+ /Engine/Source/ThirdParty/Licenses/OculusModels_License.txt
+
\ No newline at end of file
diff --git a/Plugins/MetaXR/Content/OculusModels_License.txt b/Plugins/MetaXR/Content/OculusModels_License.txt
new file mode 100644
index 0000000..4c170b2
--- /dev/null
+++ b/Plugins/MetaXR/Content/OculusModels_License.txt
@@ -0,0 +1,8 @@
+Art Attribution License 1.0
+Copyright © Facebook Technologies, LLC and its affiliates. All rights reserved.
+
+You may use these images solely for referring to the corresponding product in your video game or VR experience (including manuals for users). Otherwise, you may not use these images, or any trademarks, logos or other intellectual property owned by Facebook Technologies, LLC formerly known as Oculus VR, LLC (“Oculus”), including but not limited to use on merchandise or other product such as clothing, hats, or mugs. Do not use the Oculus images in a way that implies a partnership, sponsorship or endorsement; or features Oculus on materials associated with pornography, illegal activities, or other materials that violate Oculus Terms.
+
+THE IMAGES ARE PROVIDED TO YOU ON AN “AS IS” BASIS AND YOU ARE SOLELY RESPONSIBLE FOR YOUR USE OF THE IMAGES. OCULUS DISCLAIMS ALL WARRANTIES REGARDING THE IMAGES, INCLUDING WARRANTIES OF NON-INFRINGEMENT. OCULUS SHALL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR PUNITIVE DAMAGES ARISING FROM OR RELATED TO YOUR USE OF THE IMAGES.
+
+For the avoidance of doubt, this license shall not apply to the Oculus name, trademark or service mark, logo or design.
\ No newline at end of file
diff --git a/Plugins/MetaXR/Content/Textures/LeftMetaQuestTouchPlus_Color.uasset b/Plugins/MetaXR/Content/Textures/LeftMetaQuestTouchPlus_Color.uasset
new file mode 100644
index 0000000..397e139
--- /dev/null
+++ b/Plugins/MetaXR/Content/Textures/LeftMetaQuestTouchPlus_Color.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9d32f405dbb75078ca333884200775dc66e5401682479e1c6af9e3bf5aaea59c
+size 5144987
diff --git a/Plugins/MetaXR/Content/Textures/LeftMetaQuestTouchProBatteryIndicator.uasset b/Plugins/MetaXR/Content/Textures/LeftMetaQuestTouchProBatteryIndicator.uasset
new file mode 100644
index 0000000..9129ae1
--- /dev/null
+++ b/Plugins/MetaXR/Content/Textures/LeftMetaQuestTouchProBatteryIndicator.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b899a9f05947db5ec1674afc635d46a9c17f0c34fc99a465eb871d72028fd9ee
+size 17617
diff --git a/Plugins/MetaXR/Content/Textures/LeftMetaQuestTouchPro_Color.uasset b/Plugins/MetaXR/Content/Textures/LeftMetaQuestTouchPro_Color.uasset
new file mode 100644
index 0000000..75e1d27
--- /dev/null
+++ b/Plugins/MetaXR/Content/Textures/LeftMetaQuestTouchPro_Color.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f70c8b8e4ab9205f04cfd7cd9cffbc976232fd8c46e5232405576373abb332bc
+size 4620263
diff --git a/Plugins/MetaXR/Content/Textures/LeftMetaQuestTouchPro_Normal.uasset b/Plugins/MetaXR/Content/Textures/LeftMetaQuestTouchPro_Normal.uasset
new file mode 100644
index 0000000..b76c109
--- /dev/null
+++ b/Plugins/MetaXR/Content/Textures/LeftMetaQuestTouchPro_Normal.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:313a1d6a1cb9f2c6a423f4c92a795039ae4251f0fa3b99b4fa97215bf7753900
+size 2368859
diff --git a/Plugins/MetaXR/Content/Textures/MetaQuestTouchProBatteryIndicator.uasset b/Plugins/MetaXR/Content/Textures/MetaQuestTouchProBatteryIndicator.uasset
new file mode 100644
index 0000000..2471569
--- /dev/null
+++ b/Plugins/MetaXR/Content/Textures/MetaQuestTouchProBatteryIndicator.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:57efcf04e1ac726e768e4a41835a36eae7a2afbdbdd74c6c48c93ca6da673c01
+size 17449
diff --git a/Plugins/MetaXR/Content/Textures/RightMetaQuestTouchPlus_Color.uasset b/Plugins/MetaXR/Content/Textures/RightMetaQuestTouchPlus_Color.uasset
new file mode 100644
index 0000000..2ea3715
--- /dev/null
+++ b/Plugins/MetaXR/Content/Textures/RightMetaQuestTouchPlus_Color.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3019e678d58551f5491ff9637781176aa63fc65d5d0fdddbd1f0be6f0cf24958
+size 5110320
diff --git a/Plugins/MetaXR/Content/Textures/RightMetaQuestTouchProBatteryIndicator.uasset b/Plugins/MetaXR/Content/Textures/RightMetaQuestTouchProBatteryIndicator.uasset
new file mode 100644
index 0000000..bf66b95
--- /dev/null
+++ b/Plugins/MetaXR/Content/Textures/RightMetaQuestTouchProBatteryIndicator.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:93d12f1335d4a208ec3010e1a78f2e3970f48a9d69f84434c5ee6c9f5c648a24
+size 17624
diff --git a/Plugins/MetaXR/Content/Textures/RightMetaQuestTouchPro_Color.uasset b/Plugins/MetaXR/Content/Textures/RightMetaQuestTouchPro_Color.uasset
new file mode 100644
index 0000000..5b18886
--- /dev/null
+++ b/Plugins/MetaXR/Content/Textures/RightMetaQuestTouchPro_Color.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5f1c75cd9c92d2619bd367e7db3cfc39d920293a42a0ce210070da9410bb58d2
+size 4653519
diff --git a/Plugins/MetaXR/Content/Textures/RightMetaQuestTouchPro_Normal.uasset b/Plugins/MetaXR/Content/Textures/RightMetaQuestTouchPro_Normal.uasset
new file mode 100644
index 0000000..fb7d279
--- /dev/null
+++ b/Plugins/MetaXR/Content/Textures/RightMetaQuestTouchPro_Normal.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5abf3f6a4991c65e934ab3aa3498b68a8ee89558e3f8262141951e9ee4a78309
+size 2405558
diff --git a/Plugins/MetaXR/Content/Textures/TouchForQuest2Material_Roughness.uasset b/Plugins/MetaXR/Content/Textures/TouchForQuest2Material_Roughness.uasset
new file mode 100644
index 0000000..7aa165f
--- /dev/null
+++ b/Plugins/MetaXR/Content/Textures/TouchForQuest2Material_Roughness.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:41c13554ad2a5d4ebc96841e1427a5696b4533e365c4b2d81881a0f1fde38af6
+size 97016
diff --git a/Plugins/MetaXR/Content/Textures/TouchForQuest2_Color.uasset b/Plugins/MetaXR/Content/Textures/TouchForQuest2_Color.uasset
new file mode 100644
index 0000000..c84c3db
--- /dev/null
+++ b/Plugins/MetaXR/Content/Textures/TouchForQuest2_Color.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c6c2fc5d9188597d6570a93d484781ceddd1ee62625e7a1ea1aba49e4ca4dd03
+size 470761
diff --git a/Plugins/MetaXR/Content/Textures/TouchForQuestRiftSController_albedo.uasset b/Plugins/MetaXR/Content/Textures/TouchForQuestRiftSController_albedo.uasset
new file mode 100644
index 0000000..d4211c2
--- /dev/null
+++ b/Plugins/MetaXR/Content/Textures/TouchForQuestRiftSController_albedo.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e02ac4d4c3d1ad7fbdbc0132d47b5beee52d8cf4d050480847fda855ff69bd0e
+size 2601539
diff --git a/Plugins/MetaXR/OculusXR.uplugin b/Plugins/MetaXR/OculusXR.uplugin
new file mode 100644
index 0000000..86d6aea
--- /dev/null
+++ b/Plugins/MetaXR/OculusXR.uplugin
@@ -0,0 +1,150 @@
+{
+ "FileVersion": 3,
+ "Version": 1,
+ "VersionName": "1.96.0",
+ "FriendlyName": "Meta XR",
+ "Description": "Support for Meta Quest headsets and controllers",
+ "Category": "Virtual Reality",
+ "CreatedBy": "Meta Platforms, Inc.",
+ "CreatedByURL": "https://www.meta.com/",
+ "DocsURL": "https://developer.oculus.com/documentation/unreal/latest/concepts/unreal-engine/",
+ "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/8313d8d7e7cf4e03a33e79eb757bccba",
+ "SupportURL": "https://forums.oculusvr.com/developer",
+ "EngineVersion": "5.3.0",
+ "CanContainContent": true,
+ "Installed": true,
+ "SupportedTargetPlatforms": [
+ "Win64",
+ "Android"
+ ],
+ "Modules": [
+ {
+ "Name": "OculusXRHMD",
+ "Type": "Runtime",
+ "LoadingPhase": "PostConfigInit",
+ "PlatformAllowList": [
+ "Win64",
+ "Android"
+ ]
+ },
+ {
+ "Name": "OculusXRInput",
+ "Type": "Runtime",
+ "LoadingPhase": "Default",
+ "PlatformAllowList": [
+ "Win64",
+ "Android"
+ ]
+ },
+ {
+ "Name": "OculusXRMR",
+ "Type": "Runtime",
+ "LoadingPhase": "PostEngineInit",
+ "PlatformAllowList": [
+ "Win64",
+ "Android"
+ ]
+ },
+ {
+ "Name": "OculusXROpenXRHMD",
+ "Type": "Runtime",
+ "LoadingPhase": "PostConfigInit",
+ "PlatformAllowList": [
+ "Win64",
+ "Android"
+ ]
+ },
+ {
+ "Name": "OculusXRAnchors",
+ "Type": "Runtime",
+ "LoadingPhase": "PostEngineInit",
+ "PlatformAllowList": [
+ "Win64",
+ "Android"
+ ]
+ },
+ {
+ "Name": "OculusXRScene",
+ "Type": "Runtime",
+ "LoadingPhase": "PostEngineInit",
+ "PlatformAllowList": [
+ "Win64",
+ "Android"
+ ]
+ },
+ {
+ "Name": "OculusXRMovement",
+ "Type": "Runtime",
+ "LoadingPhase": "PostEngineInit",
+ "PlatformAllowList": [
+ "Win64",
+ "Android"
+ ]
+ },
+ {
+ "Name": "OculusXREyeTracker",
+ "Type": "Runtime",
+ "LoadingPhase": "Default",
+ "PlatformAllowList": [
+ "Win64",
+ "Android"
+ ]
+ },
+ {
+ "Name": "OculusXREditor",
+ "Type": "Editor",
+ "LoadingPhase": "Default",
+ "PlatformAllowList": [
+ "Win64"
+ ]
+ },
+ {
+ "Name": "OculusXRProjectSetupTool",
+ "Type": "Editor",
+ "LoadingPhase": "PostEngineInit",
+ "PlatformAllowList": [
+ "Win64"
+ ]
+ },
+ {
+ "Name": "OculusXRPassthrough",
+ "Type": "Runtime",
+ "LoadingPhase": "PostEngineInit",
+ "PlatformAllowList": [
+ "Win64",
+ "Android"
+ ]
+ }
+ ],
+ "Plugins": [
+ {
+ "Name": "XRBase",
+ "Enabled": true,
+ "Optional": true
+ },
+ {
+ "Name": "EnhancedInput",
+ "Enabled": true
+ },
+ {
+ "Name": "ProceduralMeshComponent",
+ "Enabled": true
+ },
+ {
+ "Name": "AndroidPermission",
+ "Enabled": true
+ },
+ {
+ "Name": "LiveLink",
+ "Enabled": true
+ },
+ {
+ "Name": "OpenXR",
+ "Enabled": false
+ },
+ {
+ "Name": "PluginBrowser",
+ "Enabled": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Plugins/MetaXR/Resources/ButtonIcon_80x.png b/Plugins/MetaXR/Resources/ButtonIcon_80x.png
new file mode 100644
index 0000000..352504a
--- /dev/null
+++ b/Plugins/MetaXR/Resources/ButtonIcon_80x.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:10ec795f2657782e8e52d97f2c63f9ec5f7cdc02ca6236f0f7300068527ca834
+size 2332
diff --git a/Plugins/MetaXR/Resources/GreenDot.svg b/Plugins/MetaXR/Resources/GreenDot.svg
new file mode 100644
index 0000000..a62cbc8
--- /dev/null
+++ b/Plugins/MetaXR/Resources/GreenDot.svg
@@ -0,0 +1,5 @@
+
+
+
diff --git a/Plugins/MetaXR/Resources/GreyDot.svg b/Plugins/MetaXR/Resources/GreyDot.svg
new file mode 100644
index 0000000..f1e3da4
--- /dev/null
+++ b/Plugins/MetaXR/Resources/GreyDot.svg
@@ -0,0 +1,5 @@
+
+
+
diff --git a/Plugins/MetaXR/Resources/Icon128.png b/Plugins/MetaXR/Resources/Icon128.png
new file mode 100644
index 0000000..17bad82
--- /dev/null
+++ b/Plugins/MetaXR/Resources/Icon128.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b6d55c2205e75a8f7ef23b37d6dfe0f4314042b56ab297c67676d1342cf705c3
+size 4209
diff --git a/Plugins/MetaXR/Resources/MetaLogo.svg b/Plugins/MetaXR/Resources/MetaLogo.svg
new file mode 100644
index 0000000..40b09f3
--- /dev/null
+++ b/Plugins/MetaXR/Resources/MetaLogo.svg
@@ -0,0 +1,3 @@
+
diff --git a/Plugins/MetaXR/Resources/MetaQuestBackground.png b/Plugins/MetaXR/Resources/MetaQuestBackground.png
new file mode 100644
index 0000000..4633a23
--- /dev/null
+++ b/Plugins/MetaXR/Resources/MetaQuestBackground.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:334961564d63bd669cd5a01790fa253f01de10720066acce83e6a2ea8c2682c2
+size 113118
diff --git a/Plugins/MetaXR/Resources/PlatformDesktop.svg b/Plugins/MetaXR/Resources/PlatformDesktop.svg
new file mode 100644
index 0000000..f5ef916
--- /dev/null
+++ b/Plugins/MetaXR/Resources/PlatformDesktop.svg
@@ -0,0 +1,3 @@
+
diff --git a/Plugins/MetaXR/Resources/PlatformQuest2.svg b/Plugins/MetaXR/Resources/PlatformQuest2.svg
new file mode 100644
index 0000000..5ee91e3
--- /dev/null
+++ b/Plugins/MetaXR/Resources/PlatformQuest2.svg
@@ -0,0 +1,7 @@
+
diff --git a/Plugins/MetaXR/Resources/PlatformQuest3.svg b/Plugins/MetaXR/Resources/PlatformQuest3.svg
new file mode 100644
index 0000000..514c730
--- /dev/null
+++ b/Plugins/MetaXR/Resources/PlatformQuest3.svg
@@ -0,0 +1,10 @@
+
diff --git a/Plugins/MetaXR/Resources/RedDot.svg b/Plugins/MetaXR/Resources/RedDot.svg
new file mode 100644
index 0000000..02baaaf
--- /dev/null
+++ b/Plugins/MetaXR/Resources/RedDot.svg
@@ -0,0 +1,5 @@
+
+
+
diff --git a/Plugins/MetaXR/Resources/WhiteDot.svg b/Plugins/MetaXR/Resources/WhiteDot.svg
new file mode 100644
index 0000000..c179483
--- /dev/null
+++ b/Plugins/MetaXR/Resources/WhiteDot.svg
@@ -0,0 +1,5 @@
+
+
+
diff --git a/Plugins/MetaXR/Resources/YellowDot.svg b/Plugins/MetaXR/Resources/YellowDot.svg
new file mode 100644
index 0000000..8b3b5b2
--- /dev/null
+++ b/Plugins/MetaXR/Resources/YellowDot.svg
@@ -0,0 +1,5 @@
+
+
+
diff --git a/Plugins/MetaXR/Shaders/Private/HardOcclusions.usf b/Plugins/MetaXR/Shaders/Private/HardOcclusions.usf
new file mode 100644
index 0000000..9eaf21d
--- /dev/null
+++ b/Plugins/MetaXR/Shaders/Private/HardOcclusions.usf
@@ -0,0 +1,36 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "/Engine/Public/Platform.ush"
+
+#define NUM_VIEWS 2
+
+// PS Textures Parameters
+Texture2DArray EnvironmentDepthTexture;
+SamplerState EnvironmentDepthSampler;
+float2 DepthFactors;
+float4x4 ScreenToDepthMatrices[NUM_VIEWS];
+int DepthViewId;
+
+void HardOcclusionsPS(
+ noperspective float4 UVAndScreenPos : TEXCOORD0,
+ float4 SvPosition : SV_POSITION,
+#if INSTANCED_STEREO
+ in uint InstanceId : SV_InstanceID,
+#elif MOBILE_MULTI_VIEW
+ in uint ViewId : SV_ViewID,
+#endif
+ out float4 OutColor : SV_Target0,
+ out float OutDepth : SV_DEPTH)
+{
+#if INSTANCED_STEREO
+ uint ViewId = InstanceId & 1;
+#elif !MOBILE_MULTI_VIEW
+ uint ViewId = DepthViewId;
+#endif
+ float4 TexCoordH = mul(ScreenToDepthMatrices[ViewId], float4(UVAndScreenPos.x, 1.0f - UVAndScreenPos.y, 0.0, 1.0));
+ float3 TexCoord = float3(TexCoordH.x / TexCoordH.w, TexCoordH.y / TexCoordH.w, ViewId);
+ float InputDepthEye = EnvironmentDepthTexture.Sample(EnvironmentDepthSampler, TexCoord).r;
+ float DepthEye = InputDepthEye * DepthFactors.x + DepthFactors.y;
+ OutDepth = DepthEye;
+ OutColor = float4(0.0, 0.0, 0.0, 1.0);
+}
diff --git a/Plugins/MetaXR/Shaders/Private/ScreenPixelShaderArraySlice.usf b/Plugins/MetaXR/Shaders/Private/ScreenPixelShaderArraySlice.usf
new file mode 100644
index 0000000..9c471cb
--- /dev/null
+++ b/Plugins/MetaXR/Shaders/Private/ScreenPixelShaderArraySlice.usf
@@ -0,0 +1,25 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "/Engine/Private/Common.ush"
+
+Texture2DArray InTexture;
+SamplerState InTextureSampler;
+int MipLevel;
+int ArraySlice;
+
+void MainMipLevel(
+ FScreenVertexOutput Input,
+ out float4 OutColor : SV_Target0
+ )
+{
+ OutColor = InTexture.SampleLevel(InTextureSampler, float3(Input.UV, ArraySlice), MipLevel);
+}
+
+void MainsRGBSourceMipLevel(
+ FScreenVertexOutput Input,
+ out float4 OutColor : SV_Target0
+ )
+{
+ OutColor = InTexture.SampleLevel(InTextureSampler, float3(Input.UV, ArraySlice), MipLevel);
+ OutColor.rgb = pow( OutColor.rgb, 1.0f / 2.2f );
+}
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/OculusXRAnchors.Build.cs b/Plugins/MetaXR/Source/OculusXRAnchors/OculusXRAnchors.Build.cs
new file mode 100644
index 0000000..2ee2aa3
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/OculusXRAnchors.Build.cs
@@ -0,0 +1,35 @@
+// @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 = true;
+
+ PrivateDependencyModuleNames.AddRange(
+ new string[]
+ {
+ "Core",
+ "CoreUObject",
+ "Engine",
+ "OculusXRHMD",
+ "OVRPluginXR",
+ "ProceduralMeshComponent",
+ });
+
+ PrivateIncludePaths.AddRange(
+ new string[] {
+ // Relative to Engine\Plugins\Runtime\Oculus\OculusVR\Source
+ "OculusXRHMD/Private",
+ });
+
+ PublicIncludePaths.AddRange(
+ new string[] {
+ "Runtime/Engine/Classes/Components",
+ });
+ }
+ }
+}
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorBPFunctionLibrary.cpp b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorBPFunctionLibrary.cpp
new file mode 100644
index 0000000..2b18bf8
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorBPFunctionLibrary.cpp
@@ -0,0 +1,225 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+#include "OculusXRAnchorBPFunctionLibrary.h"
+#include "OculusXRHMD.h"
+#include "OculusXRSpatialAnchorComponent.h"
+#include "OculusXRAnchorsPrivate.h"
+#include "OculusXRRoomLayoutManager.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();
+ if (SpatialAnchorComponent == nullptr)
+ {
+ SpatialAnchorComponent = Cast(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(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)
+{
+ OculusXRHMD::FOculusXRHMD* OutHMD = OculusXRHMD::FOculusXRHMD::GetOculusXRHMD();
+ if (!OutHMD)
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Unable to retrieve OculusXRHMD, cannot calculate anchor transform."));
+ return false;
+ }
+
+ ovrpTrackingOrigin ovrpOrigin = ovrpTrackingOrigin_EyeLevel;
+ const bool bTrackingOriginSuccess = FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetTrackingOriginType2(&ovrpOrigin));
+ if (!bTrackingOriginSuccess)
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Unable to get tracking origin, cannot calculate anchor transform."));
+ return false;
+ }
+
+ OutTransform = FTransform::Identity;
+ OutLocationFlags = FOculusXRAnchorLocationFlags(0);
+
+ const ovrpUInt64 ovrpSpace = Handle.GetValue();
+ ovrpSpaceLocationf ovrpSpaceLocation;
+
+ const bool bSuccess = FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().LocateSpace2(&ovrpSpaceLocation, &ovrpSpace, ovrpOrigin));
+ if (bSuccess)
+ {
+ OutLocationFlags = FOculusXRAnchorLocationFlags(ovrpSpaceLocation.locationFlags);
+ if (OutLocationFlags.IsValid())
+ {
+ OculusXRHMD::FPose Pose;
+ OutHMD->ConvertPose(ovrpSpaceLocation.pose, Pose);
+ const FTransform trackingToWorld = OutHMD->GetLastTrackingToWorld();
+
+ OutTransform.SetLocation(trackingToWorld.TransformPosition(Pose.Position));
+ OutTransform.SetRotation(FRotator(trackingToWorld.TransformRotation(FQuat(Pose.Orientation))).Quaternion());
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ return bSuccess;
+}
+
+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);
+#endif
+ return false;
+}
+
+const UOculusXRBaseAnchorComponent* UOculusXRAnchorBPFunctionLibrary::GetAnchorComponent(const FOculusXRSpaceQueryResult& QueryResult, EOculusXRSpaceComponentType ComponentType, UObject* Outer)
+{
+ switch (ComponentType)
+ {
+ case EOculusXRSpaceComponentType::Locatable:
+ return UOculusXRBaseAnchorComponent::FromSpace(QueryResult.Space.Value, Outer);
+ case EOculusXRSpaceComponentType::ScenePlane:
+ return UOculusXRBaseAnchorComponent::FromSpace(QueryResult.Space.Value, Outer);
+ case EOculusXRSpaceComponentType::SceneVolume:
+ return UOculusXRBaseAnchorComponent::FromSpace(QueryResult.Space.Value, Outer);
+ case EOculusXRSpaceComponentType::SemanticClassification:
+ return UOculusXRBaseAnchorComponent::FromSpace(QueryResult.Space.Value, Outer);
+ case EOculusXRSpaceComponentType::RoomLayout:
+ return UOculusXRBaseAnchorComponent::FromSpace(QueryResult.Space.Value, Outer);
+ case EOculusXRSpaceComponentType::SpaceContainer:
+ return UOculusXRBaseAnchorComponent::FromSpace(QueryResult.Space.Value, Outer);
+ case EOculusXRSpaceComponentType::Sharable:
+ return UOculusXRBaseAnchorComponent::FromSpace(QueryResult.Space.Value, Outer);
+ case EOculusXRSpaceComponentType::Storable:
+ return UOculusXRBaseAnchorComponent::FromSpace(QueryResult.Space.Value, Outer);
+ case EOculusXRSpaceComponentType::TriangleMesh:
+ return UOculusXRBaseAnchorComponent::FromSpace(QueryResult.Space.Value, Outer);
+ default:
+ return nullptr;
+ }
+}
+
+bool UOculusXRAnchorBPFunctionLibrary::GetRoomLayout(FOculusXRUInt64 Space, FOculusXRRoomLayout& RoomLayoutOut, int32 MaxWallsCapacity)
+{
+ if (MaxWallsCapacity <= 0)
+ {
+ return false;
+ }
+
+ FOculusXRUUID OutCeilingUuid;
+ FOculusXRUUID OutFloorUuid;
+ TArray OutWallsUuid;
+
+ const bool bSuccess = OculusXRAnchors::FOculusXRRoomLayoutManager::GetSpaceRoomLayout(Space.Value, static_cast(MaxWallsCapacity), OutCeilingUuid, OutFloorUuid, OutWallsUuid);
+
+ if (bSuccess)
+ {
+ RoomLayoutOut.CeilingUuid = OutCeilingUuid;
+ RoomLayoutOut.FloorUuid = OutFloorUuid;
+ RoomLayoutOut.WallsUuid.InsertZeroed(0, OutWallsUuid.Num());
+
+ for (int32 i = 0; i < OutWallsUuid.Num(); ++i)
+ {
+ RoomLayoutOut.WallsUuid[i] = OutWallsUuid[i];
+ }
+
+ TArray spaceUUIDs;
+ EOculusXRAnchorResult::Type result = OculusXRAnchors::FOculusXRAnchorManager::GetSpaceContainerUUIDs(Space, spaceUUIDs);
+
+ if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(result))
+ {
+ RoomLayoutOut.RoomObjectUUIDs = spaceUUIDs;
+ }
+ }
+
+ return bSuccess;
+}
+
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorComponent.cpp b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorComponent.cpp
new file mode 100644
index 0000000..d81ee3c
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorComponent.cpp
@@ -0,0 +1,208 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+#include "OculusXRAnchorComponent.h"
+#include "OculusXRAnchors.h"
+#include "OculusXRHMD.h"
+#include "OculusXRAnchorBPFunctionLibrary.h"
+#include "OculusXRAnchorsPrivate.h"
+#include "GameFramework/PlayerController.h"
+
+#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
+static TAutoConsoleVariable 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, *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);
+ 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(EOculusXRSpaceStorageLocation::Local),
+ StorageLocations & static_cast(EOculusXRSpaceStorageLocation::Cloud));
+
+ return (StorageLocations & static_cast(Location)) > 0;
+}
+
+void UOculusXRAnchorComponent::SetStoredLocation(EOculusXRSpaceStorageLocation Location, bool Stored)
+{
+ if (Stored)
+ {
+ StorageLocations |= static_cast(Location);
+ }
+ else
+ {
+ StorageLocations = StorageLocations & ~static_cast(Location);
+ }
+
+ UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Anchor UUID: %s - Saved Local: %d - Saved Cloud: %d"),
+ *GetUUID().ToString(),
+ StorageLocations & static_cast(EOculusXRSpaceStorageLocation::Local),
+ StorageLocations & static_cast(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;
+}
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorComponents.cpp b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorComponents.cpp
new file mode 100644
index 0000000..b54fe47
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorComponents.cpp
@@ -0,0 +1,104 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+#include "OculusXRAnchorComponents.h"
+
+#include "OculusXRAnchorBPFunctionLibrary.h"
+#include "OculusXRAnchorManager.h"
+#include "OculusXRRoomLayoutManager.h"
+#include "OculusXRSpatialAnchorComponent.h"
+
+bool UOculusXRBaseAnchorComponent::IsComponentEnabled() const
+{
+ bool OutEnabled;
+ bool OutChangePending;
+
+ auto OutResult = OculusXRAnchors::FOculusXRAnchorManager::GetSpaceComponentStatus(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 UOculusXRPlaneAnchorComponent::GetPositionAndSize(FVector& outPosition, FVector& outSize) const
+{
+ ensure(IsComponentEnabled());
+
+ if (!UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OculusXRAnchors::FOculusXRAnchorManager::GetSpaceScenePlane(Space, outPosition, outSize)))
+ {
+ UE_LOG(LogOculusSpatialAnchor, Warning, TEXT("Fetching scene plane failed."));
+ return false;
+ }
+
+ return true;
+}
+
+bool UOculusXRVolumeAnchorComponent::GetPositionAndSize(FVector& outPosition, FVector& outSize) const
+{
+ ensure(IsComponentEnabled());
+
+ if (!UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OculusXRAnchors::FOculusXRAnchorManager::GetSpaceSceneVolume(Space, outPosition, outSize)))
+ {
+ UE_LOG(LogOculusSpatialAnchor, Warning, TEXT("Fetching scene plane failed."));
+ return false;
+ }
+
+ return true;
+}
+
+bool UOculusXRSemanticClassificationAnchorComponent::GetSemanticClassifications(TArray& outClassifications) const
+{
+ ensure(IsComponentEnabled());
+
+ if (!UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OculusXRAnchors::FOculusXRAnchorManager::GetSpaceSemanticClassification(Space, outClassifications)))
+ {
+ UE_LOG(LogOculusSpatialAnchor, Warning, TEXT("Fetching scene volume failed."));
+ return false;
+ }
+
+ return true;
+}
+
+bool UOculusXRRoomLayoutAnchorComponent::GetRoomLayout(FOculusXRUUID& outFloorUUID, FOculusXRUUID& outCeilingUUID, TArray& outWallsUUIDs) const
+{
+ ensure(IsComponentEnabled());
+
+ if (!OculusXRAnchors::FOculusXRRoomLayoutManager::GetSpaceRoomLayout(Space, 64, outCeilingUUID, outFloorUUID, outWallsUUIDs))
+ {
+ UE_LOG(LogOculusSpatialAnchor, Warning, TEXT("Fetching room layout failed."));
+ return false;
+ }
+
+ return true;
+}
+
+bool UOculusXRSpaceContainerAnchorComponent::GetUUIDs(TArray& outUUIDs) const
+{
+ ensure(IsComponentEnabled());
+
+ if (!OculusXRAnchors::FOculusXRAnchorManager::GetSpaceContainer(Space, outUUIDs))
+ {
+ UE_LOG(LogOculusSpatialAnchor, Warning, TEXT("Fetching container uuids failed."));
+ return false;
+ }
+
+ return true;
+}
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorDelegates.cpp b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorDelegates.cpp
new file mode 100644
index 0000000..f264457
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorDelegates.cpp
@@ -0,0 +1,24 @@
+// 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::FOculusXRSceneCaptureCompleteDelegate FOculusXRAnchorEventDelegates::OculusSceneCaptureComplete;
+
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorLatentActions.cpp b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorLatentActions.cpp
new file mode 100644
index 0000000..940e5be
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorLatentActions.cpp
@@ -0,0 +1,581 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+#include "OculusXRAnchorLatentActions.h"
+#include "OculusXRAnchorsPrivate.h"
+#include "OculusXRHMD.h"
+#include "OculusXRAnchorBPFunctionLibrary.h"
+#include "OculusXRRoomLayoutManager.h"
+#include "OculusXRAnchorDelegates.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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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& TargetActors, EOculusXRSpaceStorageLocation StorageLocation)
+{
+ UOculusXRAsyncAction_SaveAnchorList* Action = NewObject();
+
+ auto ValidActorPtr = TargetActors.FindByPredicate([](AActor* Actor) { return IsValid(Actor); });
+
+ for (auto& it : TargetActors)
+ {
+ if (!IsValid(it))
+ {
+ continue;
+ }
+
+ UOculusXRAnchorComponent* AnchorComponent = it->FindComponentByClass();
+ Action->TargetAnchors.Add(AnchorComponent);
+ }
+
+ Action->StorageLocation = StorageLocation;
+
+ if (ValidActorPtr != nullptr)
+ {
+ Action->RegisterWithGameInstance(*ValidActorPtr);
+ }
+
+ return Action;
+}
+
+void UOculusXRAsyncAction_SaveAnchorList::HandleSaveAnchorListComplete(EOculusXRAnchorResult::Type SaveResult, const TArray& 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& UUIDs)
+{
+ FOculusXRSpaceQueryInfo QueryInfo;
+ QueryInfo.FilterType = EOculusXRSpaceQueryFilterType::FilterByIds;
+ QueryInfo.IDFilter = UUIDs;
+ QueryInfo.Location = Location;
+ QueryInfo.MaxQuerySpaces = UUIDs.Num();
+
+ UOculusXRAsyncAction_QueryAnchors* Action = NewObject();
+ Action->QueryInfo = QueryInfo;
+
+ return Action;
+}
+
+UOculusXRAsyncAction_QueryAnchors* UOculusXRAsyncAction_QueryAnchors::OculusXRAsyncQueryAnchorsAdvanced(const FOculusXRSpaceQueryInfo& QueryInfo)
+{
+ UOculusXRAsyncAction_QueryAnchors* Action = NewObject();
+ Action->QueryInfo = QueryInfo;
+
+ return Action;
+}
+
+void UOculusXRAsyncAction_QueryAnchors::HandleQueryAnchorsResults(EOculusXRAnchorResult::Type QueryResult, const TArray& 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();
+ 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();
+ 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();
+ 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& TargetActors, const TArray& ToShareWithIds)
+{
+ UOculusXRAsyncAction_ShareAnchors* Action = NewObject();
+
+ 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();
+ 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& SharedAnchors, const TArray& OculusUserIDs)
+{
+ TArray 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();
+}
+
+
+UOculusXRAnchorLaunchCaptureFlow* UOculusXRAnchorLaunchCaptureFlow::LaunchCaptureFlowAsync(const UObject* 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.
+ auto NewAction = NewObject();
+ NewAction->RegisterWithGameInstance(World->GetGameInstance());
+ return NewAction;
+}
+
+void UOculusXRAnchorLaunchCaptureFlow::Activate()
+{
+ Request = 0;
+ FOculusXRAnchorEventDelegates::OculusSceneCaptureComplete.AddUObject(this, &UOculusXRAnchorLaunchCaptureFlow::OnCaptureFinish);
+ bool CaptureStarted = OculusXRAnchors::FOculusXRRoomLayoutManager::RequestSceneCapture(Request);
+ if (!CaptureStarted)
+ {
+ FOculusXRAnchorEventDelegates::OculusSceneCaptureComplete.RemoveAll(this);
+ Failure.Broadcast();
+ }
+}
+
+void UOculusXRAnchorLaunchCaptureFlow::OnCaptureFinish(FOculusXRUInt64 RequestId, bool bSuccess)
+{
+ if (Request != RequestId.GetValue())
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("%llu request id doesn't match %llu. Ignoring request."), RequestId, Request);
+ return;
+ }
+
+ FOculusXRAnchorEventDelegates::OculusSceneCaptureComplete.RemoveAll(this);
+ Success.Broadcast();
+ SetReadyToDestroy();
+}
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorManager.cpp b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorManager.cpp
new file mode 100644
index 0000000..94d8e41
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorManager.cpp
@@ -0,0 +1,816 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+#include "OculusXRAnchorManager.h"
+
+#include
+
+#include "OculusXRHMD.h"
+#include "OculusXRAnchorsModule.h"
+#include "OculusXRAnchorDelegates.h"
+#include "OculusXRAnchorBPFunctionLibrary.h"
+#include "OculusXRAnchorTypesPrivate.h"
+
+namespace OculusXRAnchors
+{
+ OculusXRHMD::FOculusXRHMD* GetHMD(bool& OutSuccessful)
+ {
+ OculusXRHMD::FOculusXRHMD* OutHMD = OculusXRHMD::FOculusXRHMD::GetOculusXRHMD();
+ if (!OutHMD)
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Unable to retrieve OculusXRHMD"));
+ OutSuccessful = false;
+ }
+
+ OutSuccessful = true;
+
+ return OutHMD;
+ }
+
+ ovrpUuid ConvertFOculusXRUUIDtoOvrpUuid(const FOculusXRUUID& UUID)
+ {
+ ovrpUuid Result;
+ FMemory::Memcpy(Result.data, UUID.UUIDBytes);
+
+ return Result;
+ }
+
+ ovrpSpaceQueryInfo ConvertToOVRPSpaceQueryInfo(const FOculusXRSpaceQueryInfo& UEQueryInfo)
+ {
+ static const int32 MaxIdsInFilter = 1024;
+ static const int32 MaxComponentTypesInFilter = 16;
+
+ ovrpSpaceQueryInfo Result;
+
+ Result.queryType = ovrpSpaceQueryType_Action;
+ Result.actionType = ovrpSpaceQueryActionType_Load;
+
+ Result.maxQuerySpaces = UEQueryInfo.MaxQuerySpaces;
+ Result.timeout = static_cast(UEQueryInfo.Timeout); // Prevent compiler warnings, though there is a possible loss of data here.
+
+ 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;
+ }
+
+
+ Result.IdInfo.numIds = FMath::Min(MaxIdsInFilter, UEQueryInfo.IDFilter.Num());
+ for (int i = 0; i < Result.IdInfo.numIds; ++i)
+ {
+ ovrpUuid OvrUuid = ConvertFOculusXRUUIDtoOvrpUuid(UEQueryInfo.IDFilter[i]);
+ Result.IdInfo.ids[i] = OvrUuid;
+ }
+
+ 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]);
+ }
+
+ return Result;
+ }
+
+ template
+ void GetEventData(ovrpEventDataBuffer& Buffer, T& OutEventData)
+ {
+ unsigned char* BufData = Buffer.EventData;
+ BufData -= sizeof(uint64); //correct offset
+
+ memcpy(&OutEventData, BufData, sizeof(T));
+ }
+
+ void FOculusXRAnchorManager::OnPollEvent(ovrpEventDataBuffer* EventDataBuffer, bool& EventPollResult)
+ {
+ ovrpEventDataBuffer& buf = *EventDataBuffer;
+ EventPollResult = true;
+
+ switch (buf.EventType)
+ {
+ case ovrpEventType_SpatialAnchorCreateComplete:
+ {
+ ovrpEventDataSpatialAnchorCreateComplete AnchorCreateEvent;
+ GetEventData(buf, AnchorCreateEvent);
+
+ const FOculusXRUInt64 RequestId(AnchorCreateEvent.requestId);
+ const FOculusXRUInt64 Space(AnchorCreateEvent.space);
+ const FOculusXRUUID BPUUID(AnchorCreateEvent.uuid.data);
+
+ FOculusXRAnchorEventDelegates::OculusSpatialAnchorCreateComplete.Broadcast(RequestId, 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;
+ GetEventData(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,
+ 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;
+ GetEventData(buf, QueryEvent);
+
+ const FOculusXRUInt64 RequestId(QueryEvent.requestId);
+
+ FOculusXRAnchorEventDelegates::OculusSpaceQueryResults.Broadcast(RequestId);
+
+ ovrpUInt32 ovrpOutCapacity = 0;
+
+ // First get capacity
+ const bool bGetCapacityResult = FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().RetrieveSpaceQueryResults(&QueryEvent.requestId, 0, &ovrpOutCapacity, nullptr));
+
+ UE_LOG(LogOculusXRAnchors, Log, TEXT("ovrpEventType_SpaceQueryResults Request ID: %llu -- Capacity: %d -- Result: %d"), QueryEvent.requestId, ovrpOutCapacity, bGetCapacityResult);
+
+ std::vector ovrpResults(ovrpOutCapacity);
+
+ // Get Query Data
+ const bool bGetQueryDataResult = FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().RetrieveSpaceQueryResults(&QueryEvent.requestId, ovrpResults.size(), &ovrpOutCapacity, ovrpResults.data()));
+
+ for (auto queryResultElement : ovrpResults)
+ {
+ UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpaceQueryResult Space: %llu -- Result: %d"), queryResultElement.space, bGetQueryDataResult);
+
+ //translate types
+ FOculusXRUInt64 Space(queryResultElement.space);
+ FOculusXRUUID BPUUID(queryResultElement.uuid.data);
+ FOculusXRAnchorEventDelegates::OculusSpaceQueryResult.Broadcast(RequestId, Space, BPUUID);
+ }
+
+ break;
+ }
+ case ovrpEventType_SpaceQueryComplete:
+ {
+ ovrpEventSpaceQueryComplete QueryCompleteEvent;
+ GetEventData(buf, QueryCompleteEvent);
+
+ //translate to BP types
+ const FOculusXRUInt64 RequestId(QueryCompleteEvent.requestId);
+ const bool bSucceeded = QueryCompleteEvent.result >= 0;
+
+ FOculusXRAnchorEventDelegates::OculusSpaceQueryComplete.Broadcast(RequestId, QueryCompleteEvent.result);
+
+ UE_LOG(LogOculusXRAnchors, Verbose, TEXT("ovrpEventType_SpaceQueryComplete Request ID: %llu -- Result: %d"), QueryCompleteEvent.requestId, QueryCompleteEvent.result);
+
+ break;
+ }
+ case ovrpEventType_SpaceSaveComplete:
+ {
+ ovrpEventSpaceStorageSaveResult StorageResult;
+ GetEventData(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, 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;
+ GetEventData(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, SpaceListSaveResult.result);
+
+ break;
+ }
+ case ovrpEventType_SpaceEraseComplete:
+ {
+ ovrpEventSpaceStorageEraseResult SpaceEraseEvent;
+ GetEventData(buf, SpaceEraseEvent);
+
+ //translate to BP types
+ const FOculusXRUUID uuid(SpaceEraseEvent.uuid.data);
+ const FOculusXRUInt64 FRequestId(SpaceEraseEvent.requestId);
+ const FOculusXRUInt64 FResult(SpaceEraseEvent.result);
+ 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, FResult.Value, uuid, BPLocation);
+ break;
+ }
+ case ovrpEventType_SpaceShareResult:
+ {
+ unsigned char* BufData = buf.EventData;
+ ovrpUInt64 OvrpRequestId = 0;
+ memcpy(&OvrpRequestId, BufData, sizeof(OvrpRequestId));
+
+ ovrpEventSpaceShareResult SpaceShareSpaceResult;
+ GetEventData(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, SpaceShareSpaceResult.result);
+
+ break;
+ }
+ case ovrpEventType_None:
+ default:
+ {
+ EventPollResult = false;
+ break;
+ }
+ }
+ }
+
+ EOculusXRAnchorResult::Type FOculusXRAnchorManager::CreateAnchor(const FTransform& InTransform, uint64& OutRequestId, const FTransform& CameraTransform)
+ {
+ bool bValidHMD;
+ OculusXRHMD::FOculusXRHMD* HMD = GetHMD(bValidHMD);
+ if (!bValidHMD)
+ {
+ 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 static_cast(Result);
+ }
+
+ EOculusXRAnchorResult::Type FOculusXRAnchorManager::DestroySpace(uint64 Space)
+ {
+ const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().DestroySpace(static_cast(&Space));
+
+ UE_LOG(LogOculusXRAnchors, Verbose, TEXT("DestroySpace Space ID: %llu"), Space);
+
+ return static_cast(Result);
+ }
+
+ EOculusXRAnchorResult::Type FOculusXRAnchorManager::SetSpaceComponentStatus(uint64 Space, EOculusXRSpaceComponentType SpaceComponentType, bool Enable, float Timeout, uint64& OutRequestId)
+ {
+ ovrpSpaceComponentType ovrpType = ConvertToOvrpComponentType(SpaceComponentType);
+ ovrpUInt64 OvrpOutRequestId = 0;
+
+ const ovrpUInt64 OVRPSpace = Space;
+
+ // validate existing status
+ ovrpBool isEnabled = false;
+ ovrpBool changePending = false;
+ const ovrpResult getComponentStatusResult = FOculusXRHMDModule::GetPluginWrapper().GetSpaceComponentStatus(&OVRPSpace, ovrpType, &isEnabled, &changePending);
+
+ bool isStatusChangingOrSame = (static_cast(isEnabled) == Enable && !changePending) || (static_cast(isEnabled) != Enable && changePending);
+ if (OVRP_SUCCESS(getComponentStatusResult) && isStatusChangingOrSame)
+ {
+ return EOculusXRAnchorResult::Success;
+ }
+
+ // set status
+ const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().SetSpaceComponentStatus(
+ &OVRPSpace,
+ ovrpType,
+ Enable,
+ Timeout,
+ &OvrpOutRequestId);
+
+ memcpy(&OutRequestId, &OvrpOutRequestId, sizeof(uint64));
+
+ UE_LOG(LogOculusXRAnchors, Verbose, TEXT("SetSpaceComponentStatus Request ID: %llu"), OutRequestId);
+
+ return static_cast(Result);
+ }
+
+ EOculusXRAnchorResult::Type FOculusXRAnchorManager::GetSpaceComponentStatus(uint64 Space, EOculusXRSpaceComponentType SpaceComponentType, bool& OutEnabled, bool& OutChangePending)
+ {
+ const ovrpUInt64 OVRPSpace = Space;
+ ovrpBool OutOvrpEnabled = ovrpBool_False;
+ ovrpBool OutOvrpChangePending = ovrpBool_False;
+
+ ovrpSpaceComponentType ovrpType = ConvertToOvrpComponentType(SpaceComponentType);
+
+ const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceComponentStatus(
+ &OVRPSpace,
+ ovrpType,
+ &OutOvrpEnabled,
+ &OutOvrpChangePending);
+
+ OutEnabled = (OutOvrpEnabled == ovrpBool_True);
+ OutChangePending = (OutOvrpChangePending == ovrpBool_True);
+
+ return static_cast(Result);
+ }
+
+ EOculusXRAnchorResult::Type FOculusXRAnchorManager::GetSupportedAnchorComponents(uint64 Handle, TArray& OutSupportedTypes)
+ {
+ if (!FOculusXRHMDModule::GetPluginWrapper().GetInitialized())
+ {
+ return EOculusXRAnchorResult::Failure;
+ }
+
+ ovrpSpace ovrSpace = Handle;
+ TArray ovrComponentTypes;
+ ovrpUInt32 input = 0;
+ ovrpUInt32 output = 0;
+
+ ovrpResult enumerateResult = FOculusXRHMDModule::GetPluginWrapper().EnumerateSpaceSupportedComponents(&ovrSpace, input, &output, nullptr);
+ if (!OVRP_SUCCESS(enumerateResult))
+ {
+ return static_cast(enumerateResult);
+ }
+
+ input = output;
+ ovrComponentTypes.SetNumZeroed(output);
+
+ enumerateResult = FOculusXRHMDModule::GetPluginWrapper().EnumerateSpaceSupportedComponents(&ovrSpace, input, &output, ovrComponentTypes.GetData());
+ if (!OVRP_SUCCESS(enumerateResult))
+ {
+ return static_cast(enumerateResult);
+ }
+
+ OutSupportedTypes.SetNumZeroed(ovrComponentTypes.Num());
+ for (int i = 0; i < ovrComponentTypes.Num(); ++i)
+ {
+ OutSupportedTypes[i] = ConvertToUEComponentType(ovrComponentTypes[i]);
+ }
+
+ return static_cast(enumerateResult);
+ }
+
+ EOculusXRAnchorResult::Type FOculusXRAnchorManager::SaveAnchor(uint64 Space,
+ 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;
+ }
+
+ ovrpUInt64 OvrpOutRequestId = 0;
+ const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().SaveSpace(&Space, OvrpStorageLocation, OvrpStoragePersistenceMode, &OvrpOutRequestId);
+
+ UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Saving space with: SpaceID: %llu -- Location: %d -- Persistence: %d -- OutID: %llu"), Space, OvrpStorageLocation, OvrpStoragePersistenceMode, OvrpOutRequestId);
+
+ memcpy(&OutRequestId, &OvrpOutRequestId, sizeof(uint64));
+
+ if (OVRP_FAILURE(Result))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("FOculusXRHMD::SaveAnchor failed with: SpaceID: %llu -- Location: %d -- Persistence: %d"), Space, OvrpStorageLocation, OvrpStoragePersistenceMode);
+ }
+
+ return static_cast(Result);
+ }
+
+ EOculusXRAnchorResult::Type FOculusXRAnchorManager::SaveAnchorList(const TArray& Spaces, 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;
+ }
+
+ ovrpUInt64 OvrpOutRequestId = 0;
+ const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().SaveSpaceList(Spaces.GetData(), Spaces.Num(), OvrpStorageLocation, &OvrpOutRequestId);
+
+ UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Saving space list: Location: %d -- OutID: %llu"), OvrpStorageLocation, OvrpOutRequestId);
+ for (auto& it : Spaces)
+ {
+ UE_LOG(LogOculusXRAnchors, Verbose, TEXT("\tSpaceID: %llu"), it);
+ }
+
+ memcpy(&OutRequestId, &OvrpOutRequestId, sizeof(uint64));
+
+ if (OVRP_FAILURE(Result))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("SaveSpaceList failed -- Result: %d"), Result);
+ }
+
+ return static_cast(Result);
+ }
+
+ EOculusXRAnchorResult::Type FOculusXRAnchorManager::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;
+ 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 static_cast(Result);
+ }
+
+ EOculusXRAnchorResult::Type FOculusXRAnchorManager::QuerySpaces(const FOculusXRSpaceQueryInfo& QueryInfo, uint64& OutRequestId)
+ {
+ ovrpUInt64 OvrpOutRequestId = 0;
+ ovrpResult QuerySpacesResult = ovrpFailure;
+ ovrpSpaceQueryInfo ovrQueryInfo = ConvertToOVRPSpaceQueryInfo(QueryInfo);
+ QuerySpacesResult = FOculusXRHMDModule::GetPluginWrapper().QuerySpaces(&ovrQueryInfo, &OvrpOutRequestId);
+
+ memcpy(&OutRequestId, &OvrpOutRequestId, sizeof(uint64));
+
+ 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));
+ }
+ }
+
+ return static_cast(QuerySpacesResult);
+ }
+
+ EOculusXRAnchorResult::Type FOculusXRAnchorManager::ShareSpaces(const TArray& Spaces, const TArray& UserIds, uint64& OutRequestId)
+ {
+ TArray stringStorage;
+ TArray 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);
+ }
+
+ ovrpUInt64 OvrpOutRequestId = 0;
+ const ovrpResult ShareSpacesResult = FOculusXRHMDModule::GetPluginWrapper().ShareSpaces(Spaces.GetData(), Spaces.Num(), OvrpUsers.GetData(), OvrpUsers.Num(), &OvrpOutRequestId);
+
+ UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Sharing space list -- OutID: %llu"), OvrpOutRequestId);
+ 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 : Spaces)
+ {
+ UE_LOG(LogOculusXRAnchors, Verbose, TEXT("\tSpaceID: %llu"), it);
+ }
+
+ memcpy(&OutRequestId, &OvrpOutRequestId, sizeof(uint64));
+
+ return static_cast(ShareSpacesResult);
+ }
+
+ EOculusXRAnchorResult::Type FOculusXRAnchorManager::GetSpaceContainerUUIDs(uint64 Space, TArray& OutUUIDs)
+ {
+ TArray 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(&Space, &ovrSpaceContainer);
+ if (OVRP_FAILURE(result))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to get space container %d"), result);
+ return static_cast(result);
+ }
+
+ // Retrieve the actual array of UUIDs
+ ovrUuidArray.SetNum(ovrSpaceContainer.uuidCountOutput);
+ ovrSpaceContainer.uuidCapacityInput = ovrSpaceContainer.uuidCountOutput;
+ ovrSpaceContainer.uuids = ovrUuidArray.GetData();
+
+ result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceContainer(&Space, &ovrSpaceContainer);
+ if (OVRP_FAILURE(result))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to get space container %d"), result);
+ return static_cast(result);
+ }
+
+ // Write out the remaining UUIDs
+ OutUUIDs.Reserve(ovrUuidArray.Num());
+ for (auto& it : ovrUuidArray)
+ {
+ OutUUIDs.Add(FOculusXRUUID(it.data));
+ }
+
+ return EOculusXRAnchorResult::Success;
+ }
+
+ EOculusXRAnchorResult::Type FOculusXRAnchorManager::GetSpaceScenePlane(uint64 Space, FVector& OutPos, FVector& OutSize)
+ {
+ OutPos.X = OutPos.Y = OutPos.Z = 0.f;
+ OutSize.X = OutSize.Y = OutSize.Z = 0.f;
+
+ ovrpRectf rect;
+ const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceBoundingBox2D(&Space, &rect);
+
+ if (OVRP_SUCCESS(Result))
+ {
+ // Convert to UE4's coordinates system
+ OutPos.Y = rect.Pos.x;
+ OutPos.Z = rect.Pos.y;
+ OutSize.Y = rect.Size.w;
+ OutSize.Z = rect.Size.h;
+ }
+
+ return static_cast(Result);
+ }
+
+ EOculusXRAnchorResult::Type FOculusXRAnchorManager::GetSpaceSceneVolume(uint64 Space, FVector& OutPos, FVector& OutSize)
+ {
+ OutPos.X = OutPos.Y = OutPos.Z = 0.f;
+ OutSize.X = OutSize.Y = OutSize.Z = 0.f;
+
+ ovrpBoundsf bounds;
+ const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceBoundingBox3D(&Space, &bounds);
+
+ if (OVRP_SUCCESS(Result))
+ {
+ // Convert from OpenXR's right-handed to Unreal's left-handed coordinate system.
+ // OpenXR Unreal
+ // | y | z
+ // | |
+ //z <----+ +----> x
+ // / /
+ // x/ y/
+ //
+ OutPos.X = -bounds.Pos.z;
+ OutPos.Y = bounds.Pos.x;
+ OutPos.Z = bounds.Pos.y;
+
+ // The position represents the corner of the volume which has the lowest value
+ // of each axis. Since we flipped the sign of one of the axes we need to adjust
+ // the position to the other side of the volume
+ OutPos.X -= bounds.Size.d;
+
+ // We keep the size positive for all dimensions
+ OutSize.X = bounds.Size.d;
+ OutSize.Y = bounds.Size.w;
+ OutSize.Z = bounds.Size.h;
+ }
+
+ return static_cast(Result);
+ }
+
+ EOculusXRAnchorResult::Type FOculusXRAnchorManager::GetSpaceSemanticClassification(uint64 Space, TArray& OutSemanticClassifications)
+ {
+ OutSemanticClassifications.Empty();
+
+ const int32 maxByteSize = 1024;
+ char labelsChars[maxByteSize];
+
+ ovrpSemanticLabels labels;
+ labels.byteCapacityInput = maxByteSize;
+ labels.labels = labelsChars;
+
+ const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceSemanticLabels(&Space, &labels);
+
+ if (OVRP_SUCCESS(Result))
+ {
+ FString labelsStr(labels.byteCountOutput, labels.labels);
+ labelsStr.ParseIntoArray(OutSemanticClassifications, TEXT(","));
+ }
+
+ return static_cast(Result);
+ }
+
+ EOculusXRAnchorResult::Type FOculusXRAnchorManager::GetSpaceContainer(uint64 Space, TArray& OutContainerUuids)
+ {
+ OutContainerUuids.Empty();
+
+ ovrpSpaceContainer container;
+
+ ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceContainer(&Space, &container);
+
+ if (OVRP_SUCCESS(Result))
+ {
+ TArray uuids;
+ size_t size = container.uuidCountOutput;
+ uuids.InsertZeroed(0, size);
+ container.uuidCapacityInput = size;
+ container.uuids = uuids.GetData();
+ Result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceContainer(&Space, &container);
+ if (OVRP_SUCCESS(Result))
+ {
+ OutContainerUuids.InsertZeroed(0, size);
+ for (size_t i = 0; i < size; i++)
+ {
+ OutContainerUuids[i] = FOculusXRUUID(uuids[i].data);
+ }
+ }
+ }
+
+ return static_cast(Result);
+ }
+
+ EOculusXRAnchorResult::Type FOculusXRAnchorManager::GetSpaceBoundary2D(uint64 Space, TArray& OutVertices)
+ {
+ TArray vertices;
+
+ // Get the number of elements in the container
+ ovrpBoundary2D boundary;
+ boundary.vertexCapacityInput = 0;
+ boundary.vertexCountOutput = 0;
+ boundary.vertices = nullptr;
+
+ ovrpResult result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceBoundary2D(&Space, &boundary);
+ if (OVRP_FAILURE(result))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to get space boundary 2d %d"), result);
+ return static_cast(result);
+ }
+
+ // Retrieve the actual array of vertices
+ vertices.SetNum(boundary.vertexCountOutput);
+ boundary.vertexCapacityInput = boundary.vertexCountOutput;
+ boundary.vertices = vertices.GetData();
+
+ result = FOculusXRHMDModule::GetPluginWrapper().GetSpaceBoundary2D(&Space, &boundary);
+ if (OVRP_FAILURE(result))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to get space boundary 2d %d"), result);
+ return static_cast(result);
+ }
+
+ // Write out the vertices
+ OutVertices.Reserve(vertices.Num());
+ for (const auto& it : vertices)
+ {
+ OutVertices.Add(FVector2f(it.x, it.y));
+ }
+
+ return EOculusXRAnchorResult::Success;
+ }
+
+
+} // namespace OculusXRAnchors
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorManager.h b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorManager.h
new file mode 100644
index 0000000..701cbab
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorManager.h
@@ -0,0 +1,33 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "OculusXRAnchorComponent.h"
+#include "OculusXRHMDPrivate.h"
+
+namespace OculusXRAnchors
+{
+ struct OCULUSXRANCHORS_API FOculusXRAnchorManager
+ {
+ static EOculusXRAnchorResult::Type CreateAnchor(const FTransform& InTransform, uint64& OutRequestId, const FTransform& CameraTransform);
+ static EOculusXRAnchorResult::Type DestroySpace(uint64 Space);
+ static EOculusXRAnchorResult::Type SetSpaceComponentStatus(uint64 Space, EOculusXRSpaceComponentType SpaceComponentType, bool Enable, float Timeout, uint64& OutRequestId);
+ static EOculusXRAnchorResult::Type GetSpaceComponentStatus(uint64 Space, EOculusXRSpaceComponentType SpaceComponentType, bool& OutEnabled, bool& OutChangePending);
+ static EOculusXRAnchorResult::Type GetSupportedAnchorComponents(uint64 Handle, TArray& OutSupportedTypes);
+ static EOculusXRAnchorResult::Type SaveAnchor(uint64 Space, EOculusXRSpaceStorageLocation StorageLocation, EOculusXRSpaceStoragePersistenceMode StoragePersistenceMode, uint64& OutRequestId);
+ static EOculusXRAnchorResult::Type SaveAnchorList(const TArray& Spaces, EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId);
+ static EOculusXRAnchorResult::Type EraseAnchor(uint64 AnchorHandle, EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId);
+ static EOculusXRAnchorResult::Type QuerySpaces(const FOculusXRSpaceQueryInfo& QueryInfo, uint64& OutRequestId);
+ static EOculusXRAnchorResult::Type ShareSpaces(const TArray& Spaces, const TArray& UserIds, uint64& OutRequestId);
+ static EOculusXRAnchorResult::Type GetSpaceContainerUUIDs(uint64 Space, TArray& OutUUIDs);
+ static EOculusXRAnchorResult::Type GetSpaceScenePlane(uint64 Space, FVector& OutPos, FVector& OutSize);
+ static EOculusXRAnchorResult::Type GetSpaceSceneVolume(uint64 Space, FVector& OutPos, FVector& OutSize);
+ static EOculusXRAnchorResult::Type GetSpaceSemanticClassification(uint64 Space, TArray& OutSemanticClassification);
+ static EOculusXRAnchorResult::Type GetSpaceContainer(uint64 Space, TArray& OutContainerUuids);
+ static EOculusXRAnchorResult::Type GetSpaceBoundary2D(uint64 Space, TArray& OutVertices);
+
+
+ static void OnPollEvent(ovrpEventDataBuffer* EventDataBuffer, bool& EventPollResult);
+ };
+} // namespace OculusXRAnchors
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorTypes.cpp b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorTypes.cpp
new file mode 100644
index 0000000..d191a98
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorTypes.cpp
@@ -0,0 +1,96 @@
+// 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 ovrpXRUuidArray& UuidArray)
+{
+ FMemory::Memcpy(UUIDBytes, UuidArray);
+}
+
+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;
+}
+
+uint32 GetTypeHash(const FOculusXRUUID& Other)
+{
+ return FCrc::MemCrc32(&Other.UUIDBytes, sizeof(Other.UUIDBytes));
+}
+
+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);
+}
+
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorTypesPrivate.cpp b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorTypesPrivate.cpp
new file mode 100644
index 0000000..4f0142b
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorTypesPrivate.cpp
@@ -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;
+}
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorTypesPrivate.h b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorTypesPrivate.h
new file mode 100644
index 0000000..c350068
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorTypesPrivate.h
@@ -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);
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchors.cpp b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchors.cpp
new file mode 100644
index 0000000..ecb2e2d
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchors.cpp
@@ -0,0 +1,732 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+#include "OculusXRAnchors.h"
+#include "CoreMinimal.h"
+#include "Camera/PlayerCameraManager.h"
+#include "GameFramework/PlayerController.h"
+#include "OculusXRAnchorsModule.h"
+#include "OculusXRAnchorDelegates.h"
+#include "OculusXRHMDModule.h"
+#include "OculusXRAnchorManager.h"
+#include "OculusXRSpatialAnchorComponent.h"
+#include "OculusXRAnchorBPFunctionLibrary.h"
+#include "OculusXRTelemetryAnchorsEvents.h"
+
+namespace OculusXRAnchors
+{
+
+ void FOculusXRAnchors::Initialize()
+ {
+ DelegateHandleAnchorCreate = FOculusXRAnchorEventDelegates::OculusSpatialAnchorCreateComplete.AddRaw(this, &FOculusXRAnchors::HandleSpatialAnchorCreateComplete);
+ DelegateHandleAnchorErase = FOculusXRAnchorEventDelegates::OculusSpaceEraseComplete.AddRaw(this, &FOculusXRAnchors::HandleAnchorEraseComplete);
+ DelegateHandleSetComponentStatus = FOculusXRAnchorEventDelegates::OculusSpaceSetComponentStatusComplete.AddRaw(this, &FOculusXRAnchors::HandleSetComponentStatusComplete);
+ DelegateHandleAnchorSave = FOculusXRAnchorEventDelegates::OculusSpaceSaveComplete.AddRaw(this, &FOculusXRAnchors::HandleAnchorSaveComplete);
+ DelegateHandleAnchorSaveList = FOculusXRAnchorEventDelegates::OculusSpaceListSaveComplete.AddRaw(this, &FOculusXRAnchors::HandleAnchorSaveListComplete);
+ DelegateHandleQueryResultsBegin = FOculusXRAnchorEventDelegates::OculusSpaceQueryResults.AddRaw(this, &FOculusXRAnchors::HandleAnchorQueryResultsBegin);
+ DelegateHandleQueryResultElement = FOculusXRAnchorEventDelegates::OculusSpaceQueryResult.AddRaw(this, &FOculusXRAnchors::HandleAnchorQueryResultElement);
+ DelegateHandleQueryComplete = FOculusXRAnchorEventDelegates::OculusSpaceQueryComplete.AddRaw(this, &FOculusXRAnchors::HandleAnchorQueryComplete);
+ DelegateHandleAnchorShare = FOculusXRAnchorEventDelegates::OculusSpaceShareComplete.AddRaw(this, &FOculusXRAnchors::HandleAnchorSharingComplete);
+ }
+
+ void FOculusXRAnchors::Teardown()
+ {
+ FOculusXRAnchorEventDelegates::OculusSpatialAnchorCreateComplete.Remove(DelegateHandleAnchorCreate);
+ FOculusXRAnchorEventDelegates::OculusSpaceEraseComplete.Remove(DelegateHandleAnchorErase);
+ FOculusXRAnchorEventDelegates::OculusSpaceSetComponentStatusComplete.Remove(DelegateHandleSetComponentStatus);
+ FOculusXRAnchorEventDelegates::OculusSpaceSaveComplete.Remove(DelegateHandleAnchorSave);
+ FOculusXRAnchorEventDelegates::OculusSpaceListSaveComplete.Remove(DelegateHandleAnchorSaveList);
+ FOculusXRAnchorEventDelegates::OculusSpaceQueryResults.Remove(DelegateHandleQueryResultsBegin);
+ FOculusXRAnchorEventDelegates::OculusSpaceQueryResult.Remove(DelegateHandleQueryResultElement);
+ FOculusXRAnchorEventDelegates::OculusSpaceQueryComplete.Remove(DelegateHandleQueryComplete);
+ FOculusXRAnchorEventDelegates::OculusSpaceShareComplete.Remove(DelegateHandleAnchorShare);
+ }
+
+ FOculusXRAnchors* FOculusXRAnchors::GetInstance()
+ {
+ return FOculusXRAnchorsModule::GetOculusAnchors();
+ }
+
+ bool FOculusXRAnchors::CreateSpatialAnchor(const FTransform& InTransform, AActor* TargetActor, const FOculusXRSpatialAnchorCreateDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult)
+ {
+ if (!IsValid(TargetActor))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid actor provided when attempting to create a spatial anchor."));
+ OutResult = EOculusXRAnchorResult::Failure;
+ ResultCallback.ExecuteIfBound(EOculusXRAnchorResult::Failure, nullptr);
+ return false;
+ }
+
+ UWorld* World = TargetActor->GetWorld();
+ if (!IsValid(World))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Unable to retrieve World Context while creating spatial anchor."));
+ OutResult = EOculusXRAnchorResult::Failure;
+ ResultCallback.ExecuteIfBound(EOculusXRAnchorResult::Failure, nullptr);
+ return false;
+ }
+
+ APlayerController* PlayerController = World->GetFirstPlayerController();
+ if (!IsValid(PlayerController))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Unable to retrieve Player Controller while creating spatial anchor"));
+ OutResult = EOculusXRAnchorResult::Failure;
+ ResultCallback.ExecuteIfBound(EOculusXRAnchorResult::Failure, nullptr);
+ return false;
+ }
+
+ APlayerCameraManager* PlayerCameraManager = PlayerController->PlayerCameraManager;
+ FTransform MainCameraTransform = FTransform::Identity;
+ if (IsValid(PlayerCameraManager))
+ {
+ MainCameraTransform.SetLocation(PlayerCameraManager->GetCameraLocation());
+ MainCameraTransform.SetRotation(FQuat(PlayerCameraManager->GetCameraRotation()));
+ }
+
+ UOculusXRAnchorComponent* Anchor = Cast(TargetActor->GetComponentByClass(UOculusXRAnchorComponent::StaticClass()));
+ if (IsValid(Anchor) && Anchor->HasValidHandle())
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Actor targeted to create anchor already has an anchor component with a valid handle."));
+ OutResult = EOculusXRAnchorResult::Failure;
+ ResultCallback.ExecuteIfBound(EOculusXRAnchorResult::Failure, nullptr);
+ return false;
+ }
+
+ uint64 RequestId = 0;
+ OutResult = FOculusXRAnchorManager::CreateAnchor(InTransform, RequestId, MainCameraTransform);
+ bool bAsyncStartSuccess = UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OutResult);
+
+ OculusXRTelemetry::Events::FAnchorsCreateRequest Trace(static_cast(GetTypeHash(RequestId)));
+ if (bAsyncStartSuccess)
+ {
+ CreateAnchorBinding AnchorData;
+ AnchorData.RequestId = RequestId;
+ AnchorData.Actor = TargetActor;
+ AnchorData.Binding = ResultCallback;
+
+ FOculusXRAnchors* SDKInstance = GetInstance();
+ SDKInstance->CreateSpatialAnchorBindings.Add(RequestId, AnchorData);
+ }
+ else
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async create spatial anchor."));
+ ResultCallback.ExecuteIfBound(EOculusXRAnchorResult::Failure, nullptr);
+ Trace.SetResult(OculusXRTelemetry::EAction::Cancel).End();
+ }
+
+ return bAsyncStartSuccess;
+ }
+
+ bool FOculusXRAnchors::EraseAnchor(UOculusXRAnchorComponent* Anchor, const FOculusXRAnchorEraseDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult)
+ {
+ if (!IsValid(Anchor))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid anchor provided when attempting to erase an anchor."));
+ OutResult = EOculusXRAnchorResult::Failure;
+ ResultCallback.ExecuteIfBound(EOculusXRAnchorResult::Failure, FOculusXRUUID());
+ return false;
+ }
+
+ if (!Anchor->HasValidHandle())
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Cannot erase anchor with invalid handle."));
+ OutResult = EOculusXRAnchorResult::Failure;
+ ResultCallback.ExecuteIfBound(EOculusXRAnchorResult::Failure, FOculusXRUUID());
+ return false;
+ }
+
+ if (!Anchor->IsStoredAtLocation(EOculusXRSpaceStorageLocation::Local))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Only local anchors can be erased."));
+ OutResult = EOculusXRAnchorResult::Failure;
+ ResultCallback.ExecuteIfBound(EOculusXRAnchorResult::Failure, FOculusXRUUID());
+ return false;
+ }
+
+ uint64 RequestId = 0;
+
+ // Erase only supports local anchors
+ EOculusXRAnchorResult::Type Result = FOculusXRAnchorManager::EraseAnchor(Anchor->GetHandle(), EOculusXRSpaceStorageLocation::Local, RequestId);
+ bool bAsyncStartSuccess = UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(Result);
+
+ OculusXRTelemetry::Events::FAnchorsEraseRequest Trace(static_cast(GetTypeHash(RequestId)));
+ if (bAsyncStartSuccess)
+ {
+ EraseAnchorBinding EraseData;
+ EraseData.RequestId = RequestId;
+ EraseData.Binding = ResultCallback;
+ EraseData.Anchor = Anchor;
+
+ FOculusXRAnchors* SDKInstance = GetInstance();
+ SDKInstance->EraseAnchorBindings.Add(RequestId, EraseData);
+ }
+ else
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async erase spatial anchor."));
+ ResultCallback.ExecuteIfBound(EOculusXRAnchorResult::Failure, FOculusXRUUID());
+ Trace.SetResult(OculusXRTelemetry::EAction::Cancel).End();
+ }
+
+ return bAsyncStartSuccess;
+ }
+
+ bool FOculusXRAnchors::DestroyAnchor(uint64 AnchorHandle, EOculusXRAnchorResult::Type& OutResult)
+ {
+ OutResult = FOculusXRAnchorManager::DestroySpace(AnchorHandle);
+
+ return UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OutResult);
+ }
+
+ bool FOculusXRAnchors::SetAnchorComponentStatus(UOculusXRAnchorComponent* Anchor, EOculusXRSpaceComponentType SpaceComponentType, bool Enable, float Timeout, const FOculusXRAnchorSetComponentStatusDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult)
+ {
+ if (!IsValid(Anchor))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid anchor provided when attempting to set anchor component status."));
+ OutResult = EOculusXRAnchorResult::Failure;
+ ResultCallback.ExecuteIfBound(EOculusXRAnchorResult::Failure, FOculusXRUInt64(), EOculusXRSpaceComponentType::Undefined, false);
+ return false;
+ }
+
+ if (!Anchor->HasValidHandle())
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Anchor provided to set anchor component status has invalid handle."));
+ OutResult = EOculusXRAnchorResult::Failure;
+ ResultCallback.ExecuteIfBound(EOculusXRAnchorResult::Failure, FOculusXRUInt64(), EOculusXRSpaceComponentType::Undefined, false);
+ return false;
+ }
+
+ uint64 RequestId = 0;
+ OutResult = FOculusXRAnchorManager::SetSpaceComponentStatus(Anchor->GetHandle(), SpaceComponentType, Enable, Timeout, RequestId);
+ bool bAsyncStartSuccess = UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OutResult);
+
+ OculusXRTelemetry::Events::FAnchorsSetComponentStatusRequest Trace(static_cast(GetTypeHash(RequestId)));
+ if (bAsyncStartSuccess)
+ {
+ SetComponentStatusBinding SetComponentStatusData;
+ SetComponentStatusData.RequestId = RequestId;
+ SetComponentStatusData.Binding = ResultCallback;
+ SetComponentStatusData.AnchorHandle = Anchor->GetHandle();
+
+ FOculusXRAnchors* SDKInstance = GetInstance();
+ SDKInstance->SetComponentStatusBindings.Add(RequestId, SetComponentStatusData);
+ }
+ else
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async call to set anchor component status."));
+ ResultCallback.ExecuteIfBound(EOculusXRAnchorResult::Failure, FOculusXRUInt64(), EOculusXRSpaceComponentType::Undefined, false);
+ Trace.SetResult(OculusXRTelemetry::EAction::Cancel).End();
+ }
+
+ return true;
+ }
+
+ bool FOculusXRAnchors::GetAnchorComponentStatus(UOculusXRAnchorComponent* Anchor, EOculusXRSpaceComponentType SpaceComponentType, bool& OutEnabled, bool& OutChangePending, EOculusXRAnchorResult::Type& OutResult)
+ {
+ if (!IsValid(Anchor))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid anchor provided when attempting to get space component status."));
+ OutResult = EOculusXRAnchorResult::Failure;
+ return false;
+ }
+
+ if (!Anchor->HasValidHandle())
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Anchor provided to get space component status has invalid handle."));
+ OutResult = EOculusXRAnchorResult::Failure;
+ return false;
+ }
+
+ return GetComponentStatus(Anchor->GetHandle(), SpaceComponentType, OutEnabled, OutChangePending, OutResult);
+ }
+
+ bool FOculusXRAnchors::GetAnchorSupportedComponents(UOculusXRAnchorComponent* Anchor, TArray& OutSupportedComponents, EOculusXRAnchorResult::Type& OutResult)
+ {
+ if (!IsValid(Anchor))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid anchor provided when attempting to get space component status."));
+ OutResult = EOculusXRAnchorResult::Failure;
+ return false;
+ }
+
+ if (!Anchor->HasValidHandle())
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Anchor provided to get space component status has invalid handle."));
+ OutResult = EOculusXRAnchorResult::Failure;
+ return false;
+ }
+
+ return GetSupportedComponents(Anchor->GetHandle(), OutSupportedComponents, OutResult);
+ }
+
+ bool FOculusXRAnchors::SetComponentStatus(uint64 Space, EOculusXRSpaceComponentType SpaceComponentType, bool Enable, float Timeout, const FOculusXRAnchorSetComponentStatusDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult)
+ {
+ uint64 RequestId = 0;
+ OutResult = FOculusXRAnchorManager::SetSpaceComponentStatus(Space, SpaceComponentType, Enable, Timeout, RequestId);
+ bool bAsyncStartSuccess = UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OutResult);
+
+ if (bAsyncStartSuccess)
+ {
+ SetComponentStatusBinding SetComponentStatusData;
+ SetComponentStatusData.RequestId = RequestId;
+ SetComponentStatusData.Binding = ResultCallback;
+ SetComponentStatusData.AnchorHandle = Space;
+
+ FOculusXRAnchors* SDKInstance = GetInstance();
+ SDKInstance->SetComponentStatusBindings.Add(RequestId, SetComponentStatusData);
+ }
+ else
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async call to set anchor component status."));
+ ResultCallback.ExecuteIfBound(EOculusXRAnchorResult::Failure, Space, SpaceComponentType, Enable);
+ }
+
+ return true;
+ }
+
+ bool FOculusXRAnchors::GetComponentStatus(uint64 AnchorHandle, EOculusXRSpaceComponentType SpaceComponentType, bool& OutEnabled, bool& OutChangePending, EOculusXRAnchorResult::Type& OutResult)
+ {
+ OutResult = FOculusXRAnchorManager::GetSpaceComponentStatus(AnchorHandle, SpaceComponentType, OutEnabled, OutChangePending);
+ return UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OutResult);
+ }
+
+ bool FOculusXRAnchors::GetSupportedComponents(uint64 AnchorHandle, TArray& OutSupportedComponents, EOculusXRAnchorResult::Type& OutResult)
+ {
+ OutResult = FOculusXRAnchorManager::GetSupportedAnchorComponents(AnchorHandle, OutSupportedComponents);
+ return UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OutResult);
+ }
+
+ bool FOculusXRAnchors::SaveAnchor(UOculusXRAnchorComponent* Anchor, EOculusXRSpaceStorageLocation StorageLocation, const FOculusXRAnchorSaveDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult)
+ {
+ if (!IsValid(Anchor))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid anchor provided when attempting to save anchor."));
+ OutResult = EOculusXRAnchorResult::Failure;
+ ResultCallback.ExecuteIfBound(EOculusXRAnchorResult::Failure, nullptr);
+ return false;
+ }
+
+ if (!Anchor->HasValidHandle())
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Anchor provided to save anchor has invalid handle."));
+ OutResult = EOculusXRAnchorResult::Failure;
+ ResultCallback.ExecuteIfBound(EOculusXRAnchorResult::Failure, nullptr);
+ return false;
+ }
+
+ uint64 RequestId = 0;
+ OutResult = FOculusXRAnchorManager::SaveAnchor(Anchor->GetHandle(), StorageLocation, EOculusXRSpaceStoragePersistenceMode::Indefinite, RequestId);
+ bool bAsyncStartSuccess = UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OutResult);
+
+ OculusXRTelemetry::Events::FAnchorsSaveRequest Trace(static_cast(GetTypeHash(RequestId)));
+ if (bAsyncStartSuccess)
+ {
+ SaveAnchorBinding SaveAnchorData;
+ SaveAnchorData.RequestId = RequestId;
+ SaveAnchorData.Binding = ResultCallback;
+ SaveAnchorData.Location = StorageLocation;
+ SaveAnchorData.Anchor = Anchor;
+
+ FOculusXRAnchors* SDKInstance = GetInstance();
+ SDKInstance->AnchorSaveBindings.Add(RequestId, SaveAnchorData);
+ }
+ else
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async call to save anchor."));
+ ResultCallback.ExecuteIfBound(EOculusXRAnchorResult::Failure, nullptr);
+ Trace.SetResult(OculusXRTelemetry::EAction::Cancel).End();
+ }
+
+ return bAsyncStartSuccess;
+ }
+
+ void AnchorComponentsToReferences(const TArray& Anchors, TArray& Handles, TArray>& AnchorPtrs)
+ {
+ Handles.Empty();
+ AnchorPtrs.Empty();
+
+ for (auto& AnchorInstance : Anchors)
+ {
+ if (!IsValid(AnchorInstance))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid anchor provided when attempting to process anchor list."));
+ continue;
+ }
+
+ if (!AnchorInstance->HasValidHandle())
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Anchor provided to anchor list has invalid handle."));
+ continue;
+ }
+
+ Handles.Add(AnchorInstance->GetHandle().GetValue());
+ AnchorPtrs.Add(AnchorInstance);
+ }
+ }
+
+ bool FOculusXRAnchors::SaveAnchorList(const TArray& Anchors, EOculusXRSpaceStorageLocation StorageLocation, const FOculusXRAnchorSaveListDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult)
+ {
+ TArray Handles;
+ TArray> SavedAnchors;
+
+ AnchorComponentsToReferences(Anchors, Handles, SavedAnchors);
+
+ uint64 RequestId = 0;
+ OutResult = FOculusXRAnchorManager::SaveAnchorList(Handles, StorageLocation, RequestId);
+ bool bAsyncStartSuccess = UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OutResult);
+
+ if (bAsyncStartSuccess)
+ {
+ SaveAnchorListBinding SaveAnchorListData;
+ SaveAnchorListData.RequestId = RequestId;
+ SaveAnchorListData.Binding = ResultCallback;
+ SaveAnchorListData.Location = StorageLocation;
+ SaveAnchorListData.SavedAnchors = SavedAnchors;
+
+ FOculusXRAnchors* SDKInstance = GetInstance();
+ SDKInstance->AnchorSaveListBindings.Add(RequestId, SaveAnchorListData);
+ }
+ else
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async call to save anchor list."));
+ ResultCallback.ExecuteIfBound(OutResult, TArray());
+ }
+
+ return bAsyncStartSuccess;
+ }
+
+ bool FOculusXRAnchors::QueryAnchors(const TArray& AnchorUUIDs, EOculusXRSpaceStorageLocation Location, const FOculusXRAnchorQueryDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult)
+ {
+ FOculusXRSpaceQueryInfo QueryInfo;
+ QueryInfo.FilterType = EOculusXRSpaceQueryFilterType::FilterByIds;
+ QueryInfo.IDFilter = AnchorUUIDs;
+ QueryInfo.Location = Location;
+ QueryInfo.MaxQuerySpaces = AnchorUUIDs.Num();
+
+ return QueryAnchorsAdvanced(QueryInfo, ResultCallback, OutResult);
+ }
+
+ bool FOculusXRAnchors::QueryAnchorsAdvanced(const FOculusXRSpaceQueryInfo& QueryInfo, const FOculusXRAnchorQueryDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult)
+ {
+ uint64 RequestId = 0;
+ OutResult = FOculusXRAnchorManager::QuerySpaces(QueryInfo, RequestId);
+ bool bAsyncStartSuccess = UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OutResult);
+
+ OculusXRTelemetry::Events::FAnchorsQueryRequest Trace(static_cast(GetTypeHash(RequestId)));
+ if (bAsyncStartSuccess)
+ {
+ AnchorQueryBinding QueryResults;
+ QueryResults.RequestId = RequestId;
+ QueryResults.Binding = ResultCallback;
+ QueryResults.Location = QueryInfo.Location;
+
+ FOculusXRAnchors* SDKInstance = GetInstance();
+ SDKInstance->AnchorQueryBindings.Add(RequestId, QueryResults);
+ }
+ else
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async call to query anchors."));
+ ResultCallback.ExecuteIfBound(EOculusXRAnchorResult::Failure, TArray());
+ Trace.SetResult(OculusXRTelemetry::EAction::Cancel).End();
+ }
+
+ return bAsyncStartSuccess;
+ }
+
+ bool FOculusXRAnchors::ShareAnchors(const TArray& Anchors, const TArray& OculusUserIDs, const FOculusXRAnchorShareDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult)
+ {
+ TArray Handles;
+ TArray> SharedAnchors;
+
+ AnchorComponentsToReferences(Anchors, Handles, SharedAnchors);
+
+ uint64 RequestId = 0;
+ OutResult = FOculusXRAnchorManager::ShareSpaces(Handles, OculusUserIDs, RequestId);
+ bool bAsyncStartSuccess = UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OutResult);
+
+ if (bAsyncStartSuccess)
+ {
+ ShareAnchorsBinding ShareAnchorsData;
+ ShareAnchorsData.RequestId = RequestId;
+ ShareAnchorsData.Binding = ResultCallback;
+ ShareAnchorsData.SharedAnchors = SharedAnchors;
+ ShareAnchorsData.OculusUserIds = OculusUserIDs;
+
+ FOculusXRAnchors* SDKInstance = GetInstance();
+ SDKInstance->ShareAnchorsBindings.Add(RequestId, ShareAnchorsData);
+ }
+ else
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async call to share anchor."));
+ ResultCallback.ExecuteIfBound(EOculusXRAnchorResult::Failure, TArray(), TArray());
+ }
+
+ return bAsyncStartSuccess;
+ }
+
+
+ bool FOculusXRAnchors::GetSpaceContainerUUIDs(uint64 Space, TArray& OutUUIDs, EOculusXRAnchorResult::Type& OutResult)
+ {
+ OutResult = FOculusXRAnchorManager::GetSpaceContainerUUIDs(Space, OutUUIDs);
+ return UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OutResult);
+ }
+
+ bool FOculusXRAnchors::GetSpaceScenePlane(uint64 Space, FVector& OutPos, FVector& OutSize, EOculusXRAnchorResult::Type& OutResult)
+ {
+ OutResult = FOculusXRAnchorManager::GetSpaceScenePlane(Space, OutPos, OutSize);
+ return UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OutResult);
+ }
+
+ bool FOculusXRAnchors::GetSpaceSceneVolume(uint64 Space, FVector& OutPos, FVector& OutSize, EOculusXRAnchorResult::Type& OutResult)
+ {
+ OutResult = FOculusXRAnchorManager::GetSpaceSceneVolume(Space, OutPos, OutSize);
+ return UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OutResult);
+ }
+
+ bool FOculusXRAnchors::GetSpaceSemanticClassification(uint64 Space, TArray& OutSemanticClassifications, EOculusXRAnchorResult::Type& OutResult)
+ {
+ OutResult = FOculusXRAnchorManager::GetSpaceSemanticClassification(Space, OutSemanticClassifications);
+ return UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OutResult);
+ }
+
+ bool FOculusXRAnchors::GetSpaceBoundary2D(uint64 Space, TArray& OutVertices, EOculusXRAnchorResult::Type& OutResult)
+ {
+ OutResult = FOculusXRAnchorManager::GetSpaceBoundary2D(Space, OutVertices);
+ return UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(OutResult);
+ }
+
+
+ void FOculusXRAnchors::HandleSpatialAnchorCreateComplete(FOculusXRUInt64 RequestId, int Result, FOculusXRUInt64 Space, FOculusXRUUID UUID)
+ {
+ OculusXRTelemetry::Events::FAnchorsCreateResponse(static_cast(GetTypeHash(RequestId)))
+ .SetResult(OVRP_SUCCESS(Result) ? OculusXRTelemetry::EAction::Success : OculusXRTelemetry::EAction::Fail);
+ CreateAnchorBinding* AnchorDataPtr = CreateSpatialAnchorBindings.Find(RequestId.GetValue());
+ if (AnchorDataPtr == nullptr)
+ {
+ UE_LOG(LogOculusXRAnchors, Error, TEXT("Couldn't find anchor data binding for create spatial anchor! Request: %llu"), RequestId.GetValue());
+ return;
+ }
+
+ if (OVRP_FAILURE(Result))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to create Spatial Anchor. Request: %llu -- Result: %d"), RequestId.GetValue(), Result);
+ AnchorDataPtr->Binding.ExecuteIfBound(static_cast(Result), nullptr);
+ CreateSpatialAnchorBindings.Remove(RequestId.GetValue());
+ return;
+ }
+
+ if (!AnchorDataPtr->Actor.IsValid())
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Actor has been invalidated while creating actor. Request: %llu"), RequestId.GetValue());
+
+ // Clean up the orphaned space
+ EOculusXRAnchorResult::Type AnchorResult;
+ FOculusXRAnchors::DestroyAnchor(Space, AnchorResult);
+
+ AnchorDataPtr->Binding.ExecuteIfBound(static_cast(Result), nullptr);
+ CreateSpatialAnchorBindings.Remove(RequestId.GetValue());
+ return;
+ }
+
+ AActor* TargetActor = AnchorDataPtr->Actor.Get();
+
+ UOculusXRSpatialAnchorComponent* SpatialAnchorComponent = TargetActor->FindComponentByClass();
+ if (SpatialAnchorComponent == nullptr)
+ {
+ SpatialAnchorComponent = Cast(TargetActor->AddComponentByClass(UOculusXRSpatialAnchorComponent::StaticClass(), false, FTransform::Identity, false));
+ }
+
+ SpatialAnchorComponent->SetHandle(Space);
+ SpatialAnchorComponent->SetUUID(UUID);
+
+ uint64 tempOut;
+ FOculusXRAnchorManager::SetSpaceComponentStatus(Space, EOculusXRSpaceComponentType::Locatable, true, 0.0f, tempOut);
+ FOculusXRAnchorManager::SetSpaceComponentStatus(Space, EOculusXRSpaceComponentType::Sharable, true, 0.0f, tempOut);
+ FOculusXRAnchorManager::SetSpaceComponentStatus(Space, EOculusXRSpaceComponentType::Storable, true, 0.0f, tempOut);
+
+ AnchorDataPtr->Binding.ExecuteIfBound(static_cast(Result), SpatialAnchorComponent);
+ CreateSpatialAnchorBindings.Remove(RequestId.GetValue());
+ }
+
+ void FOculusXRAnchors::HandleAnchorEraseComplete(FOculusXRUInt64 RequestId, int Result, FOculusXRUUID UUID, EOculusXRSpaceStorageLocation Location)
+ {
+ OculusXRTelemetry::Events::FAnchorsEraseResponse(static_cast(GetTypeHash(RequestId)))
+ .SetResult(OVRP_SUCCESS(Result) ? OculusXRTelemetry::EAction::Success : OculusXRTelemetry::EAction::Fail);
+ EraseAnchorBinding* EraseDataPtr = EraseAnchorBindings.Find(RequestId.GetValue());
+ if (EraseDataPtr == nullptr)
+ {
+ UE_LOG(LogOculusXRAnchors, Error, TEXT("Couldn't find binding for space erase! Request: %llu"), RequestId.GetValue());
+ return;
+ }
+
+ if (OVRP_FAILURE(Result))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to erase Spatial Anchor. Request: %llu -- Result: %d"), RequestId.GetValue(), Result);
+ EraseDataPtr->Binding.ExecuteIfBound(static_cast(Result), UUID);
+ EraseAnchorBindings.Remove(RequestId.GetValue());
+ return;
+ }
+
+ if (EraseDataPtr->Anchor.IsValid())
+ {
+ // Since you can only erase local anchors, just unset local anchor storage
+ EraseDataPtr->Anchor->SetStoredLocation(EOculusXRSpaceStorageLocation::Local, false);
+ }
+
+ EraseDataPtr->Binding.ExecuteIfBound(static_cast(Result), UUID);
+ EraseAnchorBindings.Remove(RequestId.GetValue());
+ }
+
+ void FOculusXRAnchors::HandleSetComponentStatusComplete(FOculusXRUInt64 RequestId, int Result, FOculusXRUInt64 Space, FOculusXRUUID UUID, EOculusXRSpaceComponentType ComponentType, bool Enabled)
+ {
+ OculusXRTelemetry::Events::FAnchorsSetComponentStatusResponse(static_cast(GetTypeHash(RequestId)))
+ .SetResult(OVRP_SUCCESS(Result) ? OculusXRTelemetry::EAction::Success : OculusXRTelemetry::EAction::Fail);
+ SetComponentStatusBinding* SetStatusBinding = SetComponentStatusBindings.Find(RequestId.GetValue());
+
+ if (SetStatusBinding == nullptr)
+ {
+ UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Couldn't find binding for set component status! Request: %llu"), RequestId.GetValue());
+ return;
+ }
+
+ if (SetStatusBinding != nullptr)
+ {
+ SetStatusBinding->Binding.ExecuteIfBound(static_cast(Result), SetStatusBinding->AnchorHandle, ComponentType, Enabled);
+ SetComponentStatusBindings.Remove(RequestId.GetValue());
+ return;
+ }
+
+ SetStatusBinding->Binding.ExecuteIfBound(static_cast(Result), SetStatusBinding->AnchorHandle, ComponentType, Enabled);
+ SetComponentStatusBindings.Remove(RequestId.GetValue());
+ }
+
+ void FOculusXRAnchors::HandleAnchorSaveComplete(FOculusXRUInt64 RequestId, FOculusXRUInt64 Space, bool Success, int Result, FOculusXRUUID UUID)
+ {
+ OculusXRTelemetry::Events::FAnchorsSaveResponse(static_cast(GetTypeHash(RequestId)))
+ .SetResult(OVRP_SUCCESS(Result) ? OculusXRTelemetry::EAction::Success : OculusXRTelemetry::EAction::Fail);
+ SaveAnchorBinding* SaveAnchorData = AnchorSaveBindings.Find(RequestId.GetValue());
+ if (SaveAnchorData == nullptr)
+ {
+ UE_LOG(LogOculusXRAnchors, Error, TEXT("Couldn't find binding for save anchor! Request: %llu"), RequestId.GetValue());
+ return;
+ }
+
+ if (OVRP_FAILURE(Result))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to save Spatial Anchor. Request: %llu -- Result: %d -- Space: %llu"), RequestId.GetValue(), Result, Space.GetValue());
+ SaveAnchorData->Binding.ExecuteIfBound(static_cast(Result), SaveAnchorData->Anchor.Get());
+ AnchorSaveBindings.Remove(RequestId.GetValue());
+ return;
+ }
+
+ if (SaveAnchorData->Anchor.IsValid())
+ {
+ SaveAnchorData->Anchor->SetStoredLocation(SaveAnchorData->Location, true);
+ }
+
+ SaveAnchorData->Binding.ExecuteIfBound(static_cast(Result), SaveAnchorData->Anchor.Get());
+ AnchorSaveBindings.Remove(RequestId.GetValue());
+ }
+
+ void FOculusXRAnchors::HandleAnchorSaveListComplete(FOculusXRUInt64 RequestId, int Result)
+ {
+ SaveAnchorListBinding* SaveListData = AnchorSaveListBindings.Find(RequestId.GetValue());
+ if (SaveListData == nullptr)
+ {
+ UE_LOG(LogOculusXRAnchors, Error, TEXT("Couldn't find binding for save anchor list! Request: %llu"), RequestId.GetValue());
+ return;
+ }
+
+ // Get all anchors
+ TArray SavedAnchors;
+ for (auto& WeakAnchor : SaveListData->SavedAnchors)
+ {
+ if (WeakAnchor.IsValid())
+ {
+ SavedAnchors.Add(WeakAnchor.Get());
+ }
+ }
+
+ // Failed to save
+ if (OVRP_FAILURE(Result))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to save Spatial Anchors. Request: %llu -- Result: %d"), RequestId.GetValue(), Result);
+ SaveListData->Binding.ExecuteIfBound(static_cast(Result), SavedAnchors);
+ AnchorSaveListBindings.Remove(RequestId.GetValue());
+ return;
+ }
+
+ // Set new storage location
+ for (auto& SavedAnchor : SavedAnchors)
+ {
+ SavedAnchor->SetStoredLocation(SaveListData->Location, true);
+ }
+
+ SaveListData->Binding.ExecuteIfBound(static_cast(Result), SavedAnchors);
+ AnchorSaveListBindings.Remove(RequestId.GetValue());
+ }
+
+ void FOculusXRAnchors::HandleAnchorQueryResultsBegin(FOculusXRUInt64 RequestId)
+ {
+ // no op
+ }
+
+ void FOculusXRAnchors::HandleAnchorQueryResultElement(FOculusXRUInt64 RequestId, FOculusXRUInt64 Space, FOculusXRUUID UUID)
+ {
+ AnchorQueryBinding* ResultPtr = AnchorQueryBindings.Find(RequestId.GetValue());
+ if (ResultPtr)
+ {
+ uint64 tempOut;
+ TArray supportedTypes;
+ FOculusXRAnchorManager::GetSupportedAnchorComponents(Space, supportedTypes);
+
+ if (supportedTypes.Contains(EOculusXRSpaceComponentType::Locatable))
+ {
+ FOculusXRAnchorManager::SetSpaceComponentStatus(Space, EOculusXRSpaceComponentType::Locatable, true, 0.0f, tempOut);
+ }
+
+ if (supportedTypes.Contains(EOculusXRSpaceComponentType::Sharable))
+ {
+ FOculusXRAnchorManager::SetSpaceComponentStatus(Space, EOculusXRSpaceComponentType::Sharable, true, 0.0f, tempOut);
+ }
+
+ if (supportedTypes.Contains(EOculusXRSpaceComponentType::Storable))
+ {
+ FOculusXRAnchorManager::SetSpaceComponentStatus(Space, EOculusXRSpaceComponentType::Storable, true, 0.0f, tempOut);
+ }
+
+ ResultPtr->Results.Add(FOculusXRSpaceQueryResult(Space, UUID, ResultPtr->Location));
+ }
+ }
+
+ void FOculusXRAnchors::HandleAnchorQueryComplete(FOculusXRUInt64 RequestId, int Result)
+ {
+ OculusXRTelemetry::Events::FAnchorsQueryResponse(static_cast(GetTypeHash(RequestId)))
+ .SetResult(OVRP_SUCCESS(Result) ? OculusXRTelemetry::EAction::Success : OculusXRTelemetry::EAction::Fail);
+ AnchorQueryBinding* ResultPtr = AnchorQueryBindings.Find(RequestId.GetValue());
+ if (ResultPtr)
+ {
+ ResultPtr->Binding.ExecuteIfBound(static_cast(Result), ResultPtr->Results);
+ AnchorQueryBindings.Remove(RequestId.GetValue());
+ }
+ }
+
+ void FOculusXRAnchors::HandleAnchorSharingComplete(FOculusXRUInt64 RequestId, int Result)
+ {
+ ShareAnchorsBinding* ShareAnchorsData = ShareAnchorsBindings.Find(RequestId);
+ if (ShareAnchorsData == nullptr)
+ {
+ UE_LOG(LogOculusXRAnchors, Error, TEXT("Couldn't find binding for share anchors! Request: %llu"), RequestId.GetValue());
+ return;
+ }
+
+ TArray SharedAnchors;
+ for (auto& WeakAnchor : ShareAnchorsData->SharedAnchors)
+ {
+ SharedAnchors.Add(WeakAnchor.Get());
+ }
+
+ ShareAnchorsData->Binding.ExecuteIfBound(static_cast(Result), SharedAnchors, ShareAnchorsData->OculusUserIds);
+ ShareAnchorsBindings.Remove(RequestId.GetValue());
+ }
+
+
+} // namespace OculusXRAnchors
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorsModule.cpp b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorsModule.cpp
new file mode 100644
index 0000000..3e1b59a
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorsModule.cpp
@@ -0,0 +1,55 @@
+// @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 "OculusXRAnchorManager.h"
+#include "OculusXRRoomLayoutManager.h"
+
+DEFINE_LOG_CATEGORY(LogOculusXRAnchors);
+
+#define LOCTEXT_NAMESPACE "OculusXRAnchors"
+
+//-------------------------------------------------------------------------------------------------
+// FOculusXRAnchorsModule
+//-------------------------------------------------------------------------------------------------
+void FOculusXRAnchorsModule::StartupModule()
+{
+ 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::FOculusXRAnchorManager::OnPollEvent));
+ HMD->AddEventPollingDelegate(OculusXRHMD::FOculusXRHMDEventPollingDelegate::CreateStatic(&OculusXRAnchors::FOculusXRRoomLayoutManager::OnPollEvent));
+
+ Anchors.Initialize();
+}
+
+void FOculusXRAnchorsModule::ShutdownModule()
+{
+ Anchors.Teardown();
+}
+
+OculusXRAnchors::FOculusXRAnchors* FOculusXRAnchorsModule::GetOculusAnchors()
+{
+ FOculusXRAnchorsModule& Module = FModuleManager::LoadModuleChecked(TEXT("OculusXRAnchors"));
+ return &Module.Anchors;
+}
+
+#endif // OCULUS_ANCHORS_SUPPORTED_PLATFORMS
+
+IMPLEMENT_MODULE(FOculusXRAnchorsModule, OculusXRAnchors)
+
+#undef LOCTEXT_NAMESPACE
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorsModule.h b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorsModule.h
new file mode 100644
index 0000000..bb5ea9d
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorsModule.h
@@ -0,0 +1,41 @@
+// @lint-ignore-every LICENSELINT
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+#include "IOculusXRAnchorsModule.h"
+#include "OculusXRAnchors.h"
+
+#define LOCTEXT_NAMESPACE "OculusAnchors"
+
+//-------------------------------------------------------------------------------------------------
+// FOculusXRAnchorsModule
+//-------------------------------------------------------------------------------------------------
+
+#if OCULUS_ANCHORS_SUPPORTED_PLATFORMS
+
+DECLARE_LOG_CATEGORY_EXTERN(LogOculusXRAnchors, Log, All);
+
+class FOculusXRAnchorsModule : public IOculusXRAnchorsModule
+{
+public:
+ virtual ~FOculusXRAnchorsModule() = default;
+
+ // IModuleInterface interface
+ virtual void StartupModule() override;
+ virtual void ShutdownModule() override;
+
+ static OculusXRAnchors::FOculusXRAnchors* GetOculusAnchors();
+
+private:
+ OculusXRAnchors::FOculusXRAnchors Anchors;
+};
+
+#else // OCULUS_ANCHORS_SUPPORTED_PLATFORMS
+
+class FOculusXRAnchorsModule : public FDefaultModuleImpl
+{
+};
+
+#endif // OCULUS_ANCHORS_SUPPORTED_PLATFORMS
+
+#undef LOCTEXT_NAMESPACE
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorsPrivate.h b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorsPrivate.h
new file mode 100644
index 0000000..0f410f5
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRAnchorsPrivate.h
@@ -0,0 +1,5 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+#pragma once
+
+#include "OculusXRAnchorsModule.h"
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRRoomLayoutManager.cpp b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRRoomLayoutManager.cpp
new file mode 100644
index 0000000..01f5987
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRRoomLayoutManager.cpp
@@ -0,0 +1,148 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+#include "OculusXRRoomLayoutManager.h"
+#include "OculusXRHMD.h"
+#include "OculusXRAnchorDelegates.h"
+#include "OculusXRAnchorsModule.h"
+
+namespace OculusXRAnchors
+{
+ void FOculusXRRoomLayoutManager::OnPollEvent(ovrpEventDataBuffer* EventDataBuffer, bool& EventPollResult)
+ {
+ ovrpEventDataBuffer& buf = *EventDataBuffer;
+
+ switch (buf.EventType)
+ {
+ case ovrpEventType_None:
+ break;
+ case ovrpEventType_SceneCaptureComplete:
+ {
+ ovrpEventSceneCaptureComplete sceneCaptureComplete;
+ unsigned char* bufData = buf.EventData;
+
+ memcpy(&sceneCaptureComplete.requestId, bufData, sizeof(sceneCaptureComplete.requestId));
+ bufData += sizeof(ovrpUInt64); //move forward
+ memcpy(&sceneCaptureComplete.result, bufData, sizeof(sceneCaptureComplete.result));
+
+ FOculusXRAnchorEventDelegates::OculusSceneCaptureComplete.Broadcast(FOculusXRUInt64(sceneCaptureComplete.requestId), sceneCaptureComplete.result >= 0);
+ break;
+ }
+
+ default:
+ {
+ EventPollResult = false;
+ break;
+ }
+ }
+
+ EventPollResult = true;
+ }
+
+ /**
+ * @brief Requests the launch of Capture Flow
+ * @param OutRequestID The requestId returned by the system
+ * @return returns true if sucessfull
+ */
+ bool FOculusXRRoomLayoutManager::RequestSceneCapture(uint64& OutRequestID)
+ {
+ OutRequestID = 0;
+
+ ovrpSceneCaptureRequest sceneCaptureRequest;
+ sceneCaptureRequest.request = nullptr;
+ sceneCaptureRequest.requestByteCount = 0;
+
+ const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().RequestSceneCapture(&sceneCaptureRequest, &OutRequestID);
+ if (OVRP_FAILURE(Result))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @brief Gets the room layout for a specific space
+ * @param Space The space to get the room layout for
+ * @param MaxWallsCapacity Maximum number of walls to query
+ * @param OutCeilingUuid The ceiling entity's uuid
+ * @param OutFloorUuid The floor entity's uuid
+ * @param OutWallsUuid Array of uuids belonging to the walls in the room layout
+ * @return returns true if successful
+ */
+ bool FOculusXRRoomLayoutManager::GetSpaceRoomLayout(const uint64 Space, const uint32 MaxWallsCapacity,
+ FOculusXRUUID& OutCeilingUuid, FOculusXRUUID& OutFloorUuid, TArray& OutWallsUuid)
+ {
+ ovrpRoomLayout roomLayout;
+ roomLayout.wallUuidCapacityInput = 0;
+ roomLayout.wallUuidCountOutput = 0;
+
+ // First call to get output size
+ const ovrpResult firstCallResult = FOculusXRHMDModule::GetPluginWrapper().GetSpaceRoomLayout(&Space, &roomLayout);
+ if (OVRP_FAILURE(firstCallResult))
+ {
+ return false;
+ }
+
+ // Set the input size and pointer to the uuid array
+ TArray uuids;
+ uuids.InsertZeroed(0, roomLayout.wallUuidCountOutput);
+
+ roomLayout.wallUuidCapacityInput = roomLayout.wallUuidCountOutput;
+ roomLayout.wallUuids = uuids.GetData();
+
+ const ovrpResult secondCallResult = FOculusXRHMDModule::GetPluginWrapper().GetSpaceRoomLayout(&Space, &roomLayout);
+ if (OVRP_FAILURE(secondCallResult))
+ {
+ return false;
+ }
+
+ OutCeilingUuid = FOculusXRUUID(roomLayout.ceilingUuid.data);
+ OutFloorUuid = FOculusXRUUID(roomLayout.floorUuid.data);
+
+ OutWallsUuid.Empty();
+ OutWallsUuid.InsertZeroed(0, uuids.Num());
+
+ for (int32 i = 0; i < uuids.Num(); ++i)
+ {
+ OutWallsUuid[i] = FOculusXRUUID(roomLayout.wallUuids[i].data);
+ }
+
+ return true;
+ }
+
+ bool FOculusXRRoomLayoutManager::GetSpaceTriangleMesh(uint64 Space, TArray& Vertices, TArray& Triangles)
+ {
+ ovrpTriangleMesh OVRPMesh = { 0, 0, nullptr, 0, 0, nullptr };
+
+ ovrpResult CountResult = FOculusXRHMDModule::GetPluginWrapper().GetSpaceTriangleMesh(&Space, &OVRPMesh);
+ if (OVRP_FAILURE(CountResult))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to load TriangleMesh info - Space: %llu - Result: %d"), Space, CountResult);
+ return false;
+ }
+ OVRPMesh.indexCapacityInput = OVRPMesh.indexCountOutput;
+ OVRPMesh.vertexCapacityInput = OVRPMesh.vertexCountOutput;
+
+ TArray OVRPVertices;
+ OVRPVertices.SetNum(OVRPMesh.vertexCapacityInput);
+ OVRPMesh.vertices = OVRPVertices.GetData();
+ Triangles.SetNum(OVRPMesh.indexCapacityInput);
+ check(sizeof(TRemoveReference::Type::ElementType) == sizeof(TRemovePointer::Type));
+ OVRPMesh.indices = Triangles.GetData();
+
+ const ovrpResult MeshResult = FOculusXRHMDModule::GetPluginWrapper().GetSpaceTriangleMesh(&Space, &OVRPMesh);
+ if (OVRP_FAILURE(MeshResult))
+ {
+ UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to load TriangleMesh data - Space: %llu - Result: %d"), Space, MeshResult);
+ return false;
+ }
+
+ UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Loaded TriangleMesh data - Space: %llu - Vertices: %d - Faces: %d"),
+ Space, OVRPMesh.vertexCapacityInput, OVRPMesh.indexCapacityInput);
+
+ Vertices.Empty(OVRPVertices.Num());
+ Algo::Transform(OVRPVertices, Vertices, [](const auto& Vertex) { return OculusXRHMD::ToFVector(Vertex); });
+ return true;
+ return false;
+ }
+} // namespace OculusXRAnchors
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRRoomLayoutManager.h b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRRoomLayoutManager.h
new file mode 100644
index 0000000..f764b4c
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRRoomLayoutManager.h
@@ -0,0 +1,21 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "OculusXRAnchorComponent.h"
+#include "OculusXRHMDPrivate.h"
+
+namespace OculusXRAnchors
+{
+ struct FOculusXRRoomLayoutManager
+ {
+ static bool RequestSceneCapture(uint64& OutRequestID);
+ static bool GetSpaceRoomLayout(const uint64 Space, const uint32 MaxWallsCapacity,
+ FOculusXRUUID& OutCeilingUuid, FOculusXRUUID& OutFloorUuid, TArray& OutWallsUuid);
+
+ static bool GetSpaceTriangleMesh(uint64 Space, TArray& Vertices, TArray& Triangles);
+
+ static void OnPollEvent(ovrpEventDataBuffer* EventDataBuffer, bool& EventPollResult);
+ };
+} // namespace OculusXRAnchors
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRRoomLayoutManagerComponent.cpp b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRRoomLayoutManagerComponent.cpp
new file mode 100644
index 0000000..67505e8
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRRoomLayoutManagerComponent.cpp
@@ -0,0 +1,82 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+#include "OculusXRRoomLayoutManagerComponent.h"
+#include "OculusXRHMD.h"
+#include "OculusXRRoomLayoutManager.h"
+#include "OculusXRAnchorDelegates.h"
+#include "OculusXRAnchorManager.h"
+#include "OculusXRAnchorBPFunctionLibrary.h"
+#include "ProceduralMeshComponent.h"
+#include "OculusXRAnchorsModule.h"
+
+UOculusXRRoomLayoutManagerComponent::UOculusXRRoomLayoutManagerComponent(const FObjectInitializer& ObjectInitializer)
+{
+ bWantsInitializeComponent = true; // so that InitializeComponent() gets called
+}
+
+void UOculusXRRoomLayoutManagerComponent::OnRegister()
+{
+ Super::OnRegister();
+
+ FOculusXRAnchorEventDelegates::OculusSceneCaptureComplete.AddUObject(this, &UOculusXRRoomLayoutManagerComponent::OculusRoomLayoutSceneCaptureComplete_Handler);
+}
+
+void UOculusXRRoomLayoutManagerComponent::OnUnregister()
+{
+ Super::OnUnregister();
+
+ FOculusXRAnchorEventDelegates::OculusSceneCaptureComplete.RemoveAll(this);
+}
+
+void UOculusXRRoomLayoutManagerComponent::InitializeComponent()
+{
+ Super::InitializeComponent();
+}
+
+void UOculusXRRoomLayoutManagerComponent::UninitializeComponent()
+{
+ Super::UninitializeComponent();
+}
+
+bool UOculusXRRoomLayoutManagerComponent::LaunchCaptureFlow()
+{
+ UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Launch capture flow -- UOculusXRRoomLayoutManagerComponent"));
+
+ uint64 OutRequest = 0;
+ const bool bSuccess = OculusXRAnchors::FOculusXRRoomLayoutManager::RequestSceneCapture(OutRequest);
+ if (bSuccess)
+ {
+ EntityRequestList.Add(OutRequest);
+ }
+
+ UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Launch capture flow -- RequestSceneCapture -- %d"), bSuccess);
+
+ return bSuccess;
+}
+
+bool UOculusXRRoomLayoutManagerComponent::GetRoomLayout(FOculusXRUInt64 Space, FOculusXRRoomLayout& RoomLayoutOut, int32 MaxWallsCapacity)
+{
+ return UOculusXRAnchorBPFunctionLibrary::GetRoomLayout(Space, RoomLayoutOut, MaxWallsCapacity);
+}
+
+bool UOculusXRRoomLayoutManagerComponent::LoadTriangleMesh(FOculusXRUInt64 Space, UProceduralMeshComponent* Mesh, bool CreateCollision) const
+{
+ ensure(Mesh);
+ TArray Vertices;
+ TArray Triangles;
+
+ bool Success = OculusXRAnchors::FOculusXRRoomLayoutManager::GetSpaceTriangleMesh(Space, Vertices, Triangles);
+ if (!Success)
+ {
+ return false;
+ }
+
+ // Mesh->bUseAsyncCooking = true;
+ TArray EmptyNormals;
+ TArray EmptyUV;
+ TArray EmptyVertexColors;
+ TArray EmptyTangents;
+ Mesh->CreateMeshSection(0, Vertices, Triangles, EmptyNormals, EmptyUV, EmptyVertexColors, EmptyTangents, CreateCollision);
+
+ return true;
+}
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRSpatialAnchorComponent.cpp b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRSpatialAnchorComponent.cpp
new file mode 100644
index 0000000..5982856
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRSpatialAnchorComponent.cpp
@@ -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);
+}
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRSpatialAnchorManager.cpp b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRSpatialAnchorManager.cpp
new file mode 100644
index 0000000..2fd78b7
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRSpatialAnchorManager.cpp
@@ -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
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRSpatialAnchorManager.h b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRSpatialAnchorManager.h
new file mode 100644
index 0000000..5f7e9dd
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRSpatialAnchorManager.h
@@ -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
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRTelemetryAnchorsEvents.h b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRTelemetryAnchorsEvents.h
new file mode 100644
index 0000000..a7c8402
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Private/OculusXRTelemetryAnchorsEvents.h
@@ -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;
+ using FAnchorsCreateResponse = TScopedMarker;
+ using FAnchorsSetComponentStatus = TMarker<191962330>;
+ using FAnchorsSetComponentStatusRequest = TScopedMarker;
+ using FAnchorsSetComponentStatusResponse = TScopedMarker;
+ using FAnchorsSave = TMarker<191961984>;
+ using FAnchorsSaveRequest = TScopedMarker;
+ using FAnchorsSaveResponse = TScopedMarker;
+ using FAnchorsQuery = TMarker<191959258>;
+ using FAnchorsQueryRequest = TScopedMarker;
+ using FAnchorsQueryResponse = TScopedMarker;
+ using FAnchorsErase = TMarker<191960591>;
+ using FAnchorsEraseRequest = TScopedMarker;
+ using FAnchorsEraseResponse = TScopedMarker;
+} // namespace OculusXRTelemetry::Events
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Public/IOculusXRAnchorsModule.h b/Plugins/MetaXR/Source/OculusXRAnchors/Public/IOculusXRAnchorsModule.h
new file mode 100644
index 0000000..3a1129e
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Public/IOculusXRAnchorsModule.h
@@ -0,0 +1,37 @@
+// @lint-ignore-every LICENSELINT
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+#include "Modules/ModuleManager.h"
+
+#define OCULUS_ANCHORS_SUPPORTED_PLATFORMS (PLATFORM_WINDOWS && WINVER > 0x0502) || (PLATFORM_ANDROID_ARM || PLATFORM_ANDROID_ARM64)
+
+/**
+ * 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("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");
+ }
+};
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchorBPFunctionLibrary.h b/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchorBPFunctionLibrary.h
new file mode 100644
index 0000000..838828b
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchorBPFunctionLibrary.h
@@ -0,0 +1,56 @@
+// 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);
+
+ UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor")
+ static bool TryGetAnchorTransformByHandle(const FOculusXRUInt64& Handle, FTransform& OutTransform, FOculusXRAnchorLocationFlags& OutLocationFlags);
+
+ 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);
+
+ UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor")
+ static bool GetRoomLayout(FOculusXRUInt64 Space, FOculusXRRoomLayout& RoomLayoutOut, int32 MaxWallsCapacity = 64);
+
+};
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchorComponent.h b/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchorComponent.h
new file mode 100644
index 0000000..d71e502
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchorComponent.h
@@ -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;
+};
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchorComponents.h b/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchorComponents.h
new file mode 100644
index 0000000..f2b1a65
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchorComponents.h
@@ -0,0 +1,149 @@
+// 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
+ static T* FromSpace(uint64 space, UObject* Outer)
+ {
+ T* Component = NewObject(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 UOculusXRPlaneAnchorComponent : public UOculusXRBaseAnchorComponent
+{
+ GENERATED_BODY()
+public:
+ UOculusXRPlaneAnchorComponent()
+ {
+ Type = EOculusXRSpaceComponentType::ScenePlane;
+ }
+
+ UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor")
+ bool GetPositionAndSize(FVector& outPosition, FVector& outSize) const;
+};
+
+UCLASS(Blueprintable)
+class OCULUSXRANCHORS_API UOculusXRVolumeAnchorComponent : public UOculusXRBaseAnchorComponent
+{
+ GENERATED_BODY()
+public:
+ UOculusXRVolumeAnchorComponent()
+ {
+ Type = EOculusXRSpaceComponentType::SceneVolume;
+ }
+
+ UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor")
+ bool GetPositionAndSize(FVector& outPosition, FVector& outSize) const;
+};
+
+UCLASS(Blueprintable)
+class OCULUSXRANCHORS_API UOculusXRSemanticClassificationAnchorComponent : public UOculusXRBaseAnchorComponent
+{
+ GENERATED_BODY()
+public:
+ UOculusXRSemanticClassificationAnchorComponent()
+ {
+ Type = EOculusXRSpaceComponentType::SemanticClassification;
+ }
+
+ UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor")
+ bool GetSemanticClassifications(TArray& outClassifications) const;
+};
+
+UCLASS(Blueprintable)
+class OCULUSXRANCHORS_API UOculusXRRoomLayoutAnchorComponent : public UOculusXRBaseAnchorComponent
+{
+ GENERATED_BODY()
+public:
+ UOculusXRRoomLayoutAnchorComponent()
+ {
+ Type = EOculusXRSpaceComponentType::RoomLayout;
+ }
+
+ UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor")
+ bool GetRoomLayout(FOculusXRUUID& outFloorUUID, FOculusXRUUID& outCeilingUUID, TArray& outWallsUUIDs) const;
+};
+
+UCLASS(Blueprintable)
+class OCULUSXRANCHORS_API UOculusXRSpaceContainerAnchorComponent : public UOculusXRBaseAnchorComponent
+{
+ GENERATED_BODY()
+public:
+ UOculusXRSpaceContainerAnchorComponent()
+ {
+ Type = EOculusXRSpaceComponentType::SpaceContainer;
+ }
+
+ UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor")
+ bool GetUUIDs(TArray& 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;
+ }
+};
+
+UCLASS(Blueprintable)
+class OCULUSXRANCHORS_API UOculusXRTriangleMeshAnchorComponent : public UOculusXRBaseAnchorComponent
+{
+ GENERATED_BODY()
+public:
+ UOculusXRTriangleMeshAnchorComponent()
+ {
+ Type = EOculusXRSpaceComponentType::TriangleMesh;
+ }
+};
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchorDelegates.h b/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchorDelegates.h
new file mode 100644
index 0000000..b66795c
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchorDelegates.h
@@ -0,0 +1,122 @@
+// 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*/, int /*result*/, FOculusXRUInt64 /*space*/, FOculusXRUUID /*uuid*/);
+ static OCULUSXRANCHORS_API FOculusXRSpatialAnchorCreateCompleteDelegate OculusSpatialAnchorCreateComplete;
+
+ /* ovrpEventType_SpaceSetComponentStatusComplete
+ *
+ * SpaceSetComponentStatusComplete
+ * Prefix:
+ * FOculusXRSpaceSetComponentStatusComplete
+ * Suffix:
+ * FOculusXRSpaceSetComponentStatusCompleteDelegate
+ */
+ DECLARE_MULTICAST_DELEGATE_SixParams(FOculusXRSpaceSetComponentStatusCompleteDelegate, FOculusXRUInt64 /*requestId*/, int /*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*/, int /*result*/);
+ static OCULUSXRANCHORS_API FOculusXRSpaceQueryCompleteDelegate OculusSpaceQueryComplete;
+
+ /* ovrpEventType_SpaceSaveComplete
+ *
+ * SpaceSaveComplete
+ * Prefix:
+ * FOculusXRSpaceSaveComplete
+ * Suffix:
+ * FOculusXRSpaceSaveCompleteDelegate
+ */
+ DECLARE_MULTICAST_DELEGATE_FiveParams(FOculusXRSpaceSaveCompleteDelegate, FOculusXRUInt64 /*requestId*/, FOculusXRUInt64 /* space*/, bool /* sucess*/, int /*result*/, FOculusXRUUID /*uuid*/);
+ static OCULUSXRANCHORS_API FOculusXRSpaceSaveCompleteDelegate OculusSpaceSaveComplete;
+
+ /* ovrpEventType_SpaceListSaveResult
+ *
+ * SpaceListSaveComplete
+ * Prefix:
+ * FOculusSpaceListSaveComplete
+ * Suffix:
+ * FOculusSpaceListSaveCompleteDelegate
+ */
+ DECLARE_MULTICAST_DELEGATE_TwoParams(FOculusXRSpaceListSaveCompleteDelegate, FOculusXRUInt64 /*requestId*/, int /*result*/);
+ static OCULUSXRANCHORS_API FOculusXRSpaceListSaveCompleteDelegate OculusSpaceListSaveComplete;
+
+ /* ovrpEventType_SpaceEraseComplete
+ *
+ * SpaceEraseComplete
+ * Prefix:
+ * FOculusXRSpaceEraseComplete
+ * Suffix:
+ * FOculusXRSpaceEraseCompleteDelegate
+ */
+ DECLARE_MULTICAST_DELEGATE_FourParams(FOculusXRSpaceEraseCompleteDelegate, FOculusXRUInt64 /*requestId*/, int /* result*/, FOculusXRUUID /*uuid*/, EOculusXRSpaceStorageLocation /*location*/);
+ static OCULUSXRANCHORS_API FOculusXRSpaceEraseCompleteDelegate OculusSpaceEraseComplete;
+
+ /* ovrpEventType_SpaceShareSpaceResult
+ *
+ * SpaceShareComplete
+ * Prefix:
+ * FOculusSpaceShareSpacesComplete
+ * Suffix:
+ * FOculusSpaceShareSpacesCompleteDelegate
+ */
+ DECLARE_MULTICAST_DELEGATE_TwoParams(FOculusXRSpaceShareCompleteDelegate, FOculusXRUInt64 /*requestId*/, int /*result*/);
+ static OCULUSXRANCHORS_API FOculusXRSpaceShareCompleteDelegate OculusSpaceShareComplete;
+
+ /* ovrpEventType_SceneCaptureComplete
+ *
+ * SceneCaptureComplete
+ * Prefix:
+ * FOculusXRSceneCaptureComplete
+ * Suffix:
+ * FOculusXRSceneCaptureCompleteDelegate
+ */
+ DECLARE_MULTICAST_DELEGATE_TwoParams(FOculusXRSceneCaptureCompleteDelegate, FOculusXRUInt64 /*requestId*/, bool /*success*/);
+ static OCULUSXRANCHORS_API FOculusXRSceneCaptureCompleteDelegate OculusSceneCaptureComplete;
+
+};
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchorLatentActions.h b/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchorLatentActions.h
new file mode 100644
index 0000000..224cc41
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchorLatentActions.h
@@ -0,0 +1,298 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+#pragma once
+
+#include "Kismet/BlueprintAsyncActionBase.h"
+#include "Templates/SharedPointer.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&, 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&, 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&, SharedAnchors, const TArray&, UserIds, EOculusXRAnchorResult::Type, Result);
+DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusXR_LatentAction_ShareAnchors_Failure, 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& TargetActors, EOculusXRSpaceStorageLocation StorageLocation);
+
+ UPROPERTY(BlueprintAssignable)
+ FOculusXR_LatentAction_SaveAnchorList_Success Success;
+
+ UPROPERTY(BlueprintAssignable)
+ FOculusXR_LatentAction_SaveAnchorList_Failure Failure;
+
+ UPROPERTY(Transient)
+ TArray TargetAnchors;
+
+ EOculusXRSpaceStorageLocation StorageLocation;
+
+private:
+ void HandleSaveAnchorListComplete(EOculusXRAnchorResult::Type SaveResult, const TArray& 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& 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 QueryResults;
+
+private:
+ void HandleQueryAnchorsResults(EOculusXRAnchorResult::Type QueryResult, const TArray& 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& TargetActors, const TArray& ToShareWithIds);
+
+ UPROPERTY(BlueprintAssignable)
+ FOculusXR_LatentAction_ShareAnchors_Success Success;
+
+ UPROPERTY(BlueprintAssignable)
+ FOculusXR_LatentAction_ShareAnchors_Failure Failure;
+
+ // Target Spaces
+ UPROPERTY(Transient)
+ TArray TargetAnchors;
+
+ // Users to share with
+ TArray ToShareWithIds;
+
+ FOculusXRUInt64 ShareSpacesRequestId;
+
+private:
+ void HandleShareAnchorsComplete(EOculusXRAnchorResult::Type ShareResult, const TArray& TargetAnchors, const TArray& OculusUserIds);
+};
+
+
+UCLASS()
+class OCULUSXRANCHORS_API UOculusXRAnchorLaunchCaptureFlow : public UBlueprintAsyncActionBase
+{
+ GENERATED_BODY()
+public:
+ DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOculusXRAnchorCaptureFlowFinished);
+
+ UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor", meta = (WorldContext = "WorldContext", BlueprintInternalUseOnly = "true"))
+ static UOculusXRAnchorLaunchCaptureFlow* LaunchCaptureFlowAsync(const UObject* WorldContext);
+
+ void Activate() override;
+
+ UPROPERTY(BlueprintAssignable)
+ FOculusXRAnchorCaptureFlowFinished Success;
+
+ UPROPERTY(BlueprintAssignable)
+ FOculusXRAnchorCaptureFlowFinished Failure;
+
+private:
+ uint64 Request = 0;
+
+ UFUNCTION(CallInEditor)
+ void OnCaptureFinish(FOculusXRUInt64 RequestId, bool bSuccess);
+};
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchorTypes.h b/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchorTypes.h
new file mode 100644
index 0000000..d19edc0
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchorTypes.h
@@ -0,0 +1,293 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+#pragma once
+#include
+#include "OculusXRAnchorTypes.generated.h"
+
+#define OCULUSXR_UUID_SIZE 16
+
+typedef uint8 ovrpXRUuidArray[OCULUSXR_UUID_SIZE];
+
+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,
+
+
+
+ };
+} // 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(EOculusLocationFlags::OrientationValid);
+ }
+
+ bool PositionValid() const
+ {
+ return Flags & static_cast(EOculusLocationFlags::PositionValid);
+ }
+
+ bool OrientationTracked() const
+ {
+ return Flags & static_cast(EOculusLocationFlags::OrientationTracked);
+ }
+
+ bool PositionTracked() const
+ {
+ return Flags & static_cast(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 ovrpXRUuidArray& UuidArray);
+
+ 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);
+ 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 : public TStructOpsTypeTraitsBase2
+{
+ 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; };
+};
+
+template <>
+struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2
+{
+ 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"),
+};
+
+// 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 IDFilter;
+
+ UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor")
+ TArray ComponentFilter;
+};
+
+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 Uuids; // used if filtering by UUIDs
+ TArray ComponentTypes; // used if filtering by component types
+};
+
+
+// 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 WallsUuid;
+
+ UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Anchors")
+ TArray RoomObjectUUIDs;
+};
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchors.h b/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchors.h
new file mode 100644
index 0000000..345dbc9
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRAnchors.h
@@ -0,0 +1,146 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "OculusXRAnchorComponent.h"
+#include "OculusXRAnchorTypes.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& /*SavedAnchors*/);
+DECLARE_DELEGATE_TwoParams(FOculusXRAnchorQueryDelegate, EOculusXRAnchorResult::Type /*Result*/, const TArray& /*Results*/);
+DECLARE_DELEGATE_ThreeParams(FOculusXRAnchorShareDelegate, EOculusXRAnchorResult::Type /*Result*/, const TArray& /*Anchors*/, const TArray& /*Users*/);
+
+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& 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& OutSupportedComponents, EOculusXRAnchorResult::Type& OutResult);
+
+ static bool SaveAnchor(UOculusXRAnchorComponent* Anchor, EOculusXRSpaceStorageLocation StorageLocation, const FOculusXRAnchorSaveDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult);
+ static bool SaveAnchorList(const TArray& Anchors, EOculusXRSpaceStorageLocation StorageLocation, const FOculusXRAnchorSaveListDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult);
+
+ static bool QueryAnchors(const TArray& 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& Anchors, const TArray& OculusUserIDs, const FOculusXRAnchorShareDelegate& ResultCallback, EOculusXRAnchorResult::Type& OutResult);
+
+ static bool GetSpaceContainerUUIDs(uint64 Space, TArray& OutUUIDs, EOculusXRAnchorResult::Type& OutResult);
+ static bool GetSpaceScenePlane(uint64 Space, FVector& OutPos, FVector& OutSize, EOculusXRAnchorResult::Type& OutResult);
+ static bool GetSpaceSceneVolume(uint64 Space, FVector& OutPos, FVector& OutSize, EOculusXRAnchorResult::Type& OutResult);
+ static bool GetSpaceSemanticClassification(uint64 Space, TArray& OutSemanticClassifications, EOculusXRAnchorResult::Type& OutResult);
+ static bool GetSpaceBoundary2D(uint64 Space, TArray& OutVertices, EOculusXRAnchorResult::Type& OutResult);
+
+
+
+ private:
+ void HandleSpatialAnchorCreateComplete(FOculusXRUInt64 RequestId, int Result, FOculusXRUInt64 Space, FOculusXRUUID UUID);
+ void HandleAnchorEraseComplete(FOculusXRUInt64 RequestId, int Result, FOculusXRUUID UUID, EOculusXRSpaceStorageLocation Location);
+
+ void HandleSetComponentStatusComplete(FOculusXRUInt64 RequestId, int Result, FOculusXRUInt64 Space, FOculusXRUUID UUID, EOculusXRSpaceComponentType ComponentType, bool Enabled);
+
+ void HandleAnchorSaveComplete(FOculusXRUInt64 RequestId, FOculusXRUInt64 Space, bool Success, int Result, FOculusXRUUID UUID);
+ void HandleAnchorSaveListComplete(FOculusXRUInt64 RequestId, int Result);
+
+ void HandleAnchorQueryResultsBegin(FOculusXRUInt64 RequestId);
+ void HandleAnchorQueryResultElement(FOculusXRUInt64 RequestId, FOculusXRUInt64 Space, FOculusXRUUID UUID);
+ void HandleAnchorQueryComplete(FOculusXRUInt64 RequestId, int Result);
+
+ void HandleAnchorSharingComplete(FOculusXRUInt64 RequestId, int Result);
+
+
+ struct EraseAnchorBinding
+ {
+ FOculusXRUInt64 RequestId;
+ FOculusXRAnchorEraseDelegate Binding;
+ TWeakObjectPtr Anchor;
+ };
+
+ struct SetComponentStatusBinding
+ {
+ FOculusXRUInt64 RequestId;
+ FOculusXRAnchorSetComponentStatusDelegate Binding;
+ uint64 AnchorHandle;
+ };
+
+ struct CreateAnchorBinding
+ {
+ FOculusXRUInt64 RequestId;
+ FOculusXRSpatialAnchorCreateDelegate Binding;
+ TWeakObjectPtr Actor;
+ };
+
+ struct SaveAnchorBinding
+ {
+ FOculusXRUInt64 RequestId;
+ FOculusXRAnchorSaveDelegate Binding;
+ EOculusXRSpaceStorageLocation Location;
+ TWeakObjectPtr Anchor;
+ };
+
+ struct SaveAnchorListBinding
+ {
+ FOculusXRUInt64 RequestId;
+ FOculusXRAnchorSaveListDelegate Binding;
+ EOculusXRSpaceStorageLocation Location;
+ TArray> SavedAnchors;
+ };
+
+ struct AnchorQueryBinding
+ {
+ FOculusXRUInt64 RequestId;
+ FOculusXRAnchorQueryDelegate Binding;
+ EOculusXRSpaceStorageLocation Location;
+ TArray Results;
+ };
+
+ struct ShareAnchorsBinding
+ {
+ FOculusXRUInt64 RequestId;
+ FOculusXRAnchorShareDelegate Binding;
+ TArray> SharedAnchors;
+ TArray OculusUserIds;
+ };
+
+
+ // Delegate bindings
+ TMap CreateSpatialAnchorBindings;
+ TMap EraseAnchorBindings;
+ TMap SetComponentStatusBindings;
+ TMap AnchorSaveBindings;
+ TMap AnchorSaveListBindings;
+ TMap AnchorQueryBindings;
+ TMap ShareAnchorsBindings;
+
+ // Delegate handles
+ FDelegateHandle DelegateHandleAnchorCreate;
+ FDelegateHandle DelegateHandleAnchorErase;
+ FDelegateHandle DelegateHandleSetComponentStatus;
+ FDelegateHandle DelegateHandleAnchorSave;
+ FDelegateHandle DelegateHandleAnchorSaveList;
+ FDelegateHandle DelegateHandleQueryResultsBegin;
+ FDelegateHandle DelegateHandleQueryResultElement;
+ FDelegateHandle DelegateHandleQueryComplete;
+ FDelegateHandle DelegateHandleAnchorShare;
+ };
+
+} // namespace OculusXRAnchors
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRRoomLayoutManagerComponent.h b/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRRoomLayoutManagerComponent.h
new file mode 100644
index 0000000..28827d2
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRRoomLayoutManagerComponent.h
@@ -0,0 +1,64 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "OculusXRSpatialAnchorComponent.h"
+#include "OculusXRAnchorBPFunctionLibrary.h"
+#include "OculusXRRoomLayoutManagerComponent.generated.h"
+
+UCLASS(meta = (DisplayName = "OculusXR Room Layout Manager Component", BlueprintSpawnableComponent))
+class OCULUSXRANCHORS_API UOculusXRRoomLayoutManagerComponent : public UActorComponent
+{
+ GENERATED_BODY()
+
+public:
+ UOculusXRRoomLayoutManagerComponent(const FObjectInitializer& ObjectInitializer);
+
+ virtual void InitializeComponent() override;
+ virtual void UninitializeComponent() override;
+
+ virtual void OnRegister() override;
+ virtual void OnUnregister() override;
+
+ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOculusXRRoomLayoutSceneCaptureCompleteDelegate,
+ FOculusXRUInt64, requestId,
+ bool, result);
+
+ DECLARE_MULTICAST_DELEGATE_TwoParams(FOculusXRRoomLayoutSceneCompleteNativeDelegate, FOculusXRUInt64 /*requestId*/, bool /*success*/);
+ FOculusXRRoomLayoutSceneCompleteNativeDelegate OculusXRRoomLayoutSceneCaptureCompleteNative;
+
+ UPROPERTY(BlueprintAssignable, Category = "OculusXR|Room Layout Manager")
+ FOculusXRRoomLayoutSceneCaptureCompleteDelegate OculusXRRoomLayoutSceneCaptureComplete;
+
+ // Requests to launch Capture Flow
+ UFUNCTION(BlueprintCallable, Category = "OculusXR|Room Layout Manager")
+ bool LaunchCaptureFlow();
+
+ // Gets room layout for a specific space
+ UFUNCTION(BlueprintCallable, Category = "OculusXR|Room Layout Manager")
+ bool GetRoomLayout(FOculusXRUInt64 Space, UPARAM(ref) FOculusXRRoomLayout& RoomLayoutOut, int32 MaxWallsCapacity = 64);
+
+ // Loads mesh data (vertices, indeces) associated with the space into UProceduralMeshComponent
+ UFUNCTION(BlueprintCallable, Category = "OculusXR|Room Layout Manager")
+ bool LoadTriangleMesh(FOculusXRUInt64 Space, class UProceduralMeshComponent* Mesh, bool CreateCollision) const;
+
+protected:
+ UPROPERTY(Transient)
+ TSet EntityRequestList;
+
+ UPROPERTY(Transient)
+ TMap RoomLayouts;
+
+private:
+ UFUNCTION()
+ void OculusRoomLayoutSceneCaptureComplete_Handler(FOculusXRUInt64 RequestId, bool bSuccess)
+ {
+ if (EntityRequestList.Find(RequestId.Value) != nullptr)
+ {
+ OculusXRRoomLayoutSceneCaptureComplete.Broadcast(RequestId, bSuccess);
+ OculusXRRoomLayoutSceneCaptureCompleteNative.Broadcast(RequestId, bSuccess);
+ EntityRequestList.Remove(RequestId.Value);
+ }
+ }
+};
diff --git a/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRSpatialAnchorComponent.h b/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRSpatialAnchorComponent.h
new file mode 100644
index 0000000..15a63fb
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXRAnchors/Public/OculusXRSpatialAnchorComponent.h
@@ -0,0 +1,25 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "OculusXRAnchorComponent.h"
+#include "OculusXRAnchors.h"
+#include "OculusXRSpatialAnchorComponent.generated.h"
+
+DECLARE_LOG_CATEGORY_EXTERN(LogOculusSpatialAnchor, Log, All);
+
+UCLASS(meta = (DisplayName = "Oculus Spatial Anchor Component", BlueprintSpawnableComponent))
+class OCULUSXRANCHORS_API UOculusXRSpatialAnchorComponent : public UOculusXRAnchorComponent
+{
+ GENERATED_BODY()
+public:
+ UOculusXRSpatialAnchorComponent(const FObjectInitializer& ObjectInitializer);
+
+ static bool Create(const FTransform& NewAnchorTransform, AActor* OwningActor, const FOculusXRSpatialAnchorCreateDelegate& Callback);
+
+ bool Erase(const FOculusXRAnchorEraseDelegate& Callback);
+ bool Save(EOculusXRSpaceStorageLocation Location, const FOculusXRAnchorSaveDelegate& Callback);
+
+private:
+};
diff --git a/Plugins/MetaXR/Source/OculusXREditor/OculusXREditor.Build.cs b/Plugins/MetaXR/Source/OculusXREditor/OculusXREditor.Build.cs
new file mode 100644
index 0000000..e850681
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXREditor/OculusXREditor.Build.cs
@@ -0,0 +1,55 @@
+// @lint-ignore-every LICENSELINT
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+using UnrealBuildTool;
+
+public class OculusXREditor : ModuleRules
+{
+ public OculusXREditor(ReadOnlyTargetRules Target) : base(Target)
+ {
+ bUseUnity = true;
+
+ PrivateDependencyModuleNames.AddRange(
+ new string[] {
+ "Projects",
+ "InputCore",
+ "UnrealEd",
+ "LevelEditor",
+ "CoreUObject",
+ "Engine",
+ "EngineSettings",
+ "AndroidRuntimeSettings",
+ "Slate",
+ "SlateCore",
+ "EditorStyle",
+ "Core",
+ "OculusXRHMD",
+ "OculusXRMovement",
+ "OculusXRPassthrough",
+ "OVRPluginXR",
+ "OculusXRProjectSetupTool",
+ "HTTP",
+ "DesktopPlatform",
+ "LauncherServices",
+ "GameProjectGeneration",
+ "SharedSettingsWidgets",
+ "RHI",
+ "SourceControl",
+ }
+ );
+
+ PrivateIncludePaths.AddRange(
+ new string[] {
+ // Relative to Engine\Plugins\Runtime\Oculus\OculusVR\Source
+ "OculusXREditor/Private",
+ "OculusXRHMD/Private"
+ });
+
+ PrivateIncludePathModuleNames.AddRange(
+ new string[] {
+ "Settings",
+ "OculusXRProjectSetupTool"
+ }
+ );
+ }
+}
diff --git a/Plugins/MetaXR/Source/OculusXREditor/Private/OculusXRBuildAnalytics.cpp b/Plugins/MetaXR/Source/OculusXREditor/Private/OculusXRBuildAnalytics.cpp
new file mode 100644
index 0000000..8d868e4
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXREditor/Private/OculusXRBuildAnalytics.cpp
@@ -0,0 +1,325 @@
+// @lint-ignore-every LICENSELINT
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "OculusXRBuildAnalytics.h"
+#include "GameProjectGenerationModule.h"
+#include "OculusXRHMDModule.h"
+#include "Runtime/Core/Public/HAL/FileManager.h"
+
+FOculusBuildAnalytics* FOculusBuildAnalytics::instance = 0;
+
+FOculusBuildAnalytics* FOculusBuildAnalytics::GetInstance()
+{
+ if (IOculusXRHMDModule::IsAvailable())
+ {
+ if (instance == nullptr)
+ {
+ instance = new FOculusBuildAnalytics();
+ }
+ }
+
+ return instance;
+}
+
+bool FOculusBuildAnalytics::IsOculusXRHMDAvailable()
+{
+ return IOculusXRHMDModule::IsAvailable() && FOculusXRHMDModule::Get().PreInit();
+}
+
+void FOculusBuildAnalytics::Shutdown()
+{
+}
+
+FOculusBuildAnalytics::FOculusBuildAnalytics()
+{
+ bool TelemetryEnabled = false;
+ if (!GConfig->GetBool(TEXT("/Script/OculusXREditor.OculusXREditorSettings"), TEXT("bEnableOculusBuildTelemetry"), TelemetryEnabled, GEditorIni))
+ {
+ GConfig->SetBool(TEXT("/Script/OculusXREditor.OculusXREditorSettings"), TEXT("bEnableOculusBuildTelemetry"), TelemetryEnabled, GEditorIni);
+ GConfig->Flush(0);
+ }
+
+ if (TelemetryEnabled)
+ {
+ RegisterLauncherCallback();
+ }
+}
+
+void FOculusBuildAnalytics::OnTelemetryToggled(bool Enabled)
+{
+ if (Enabled)
+ {
+ RegisterLauncherCallback();
+ }
+ else
+ {
+ if (LauncherCallbackHandle.IsValid())
+ {
+ ILauncherServicesModule& ProjectLauncherServicesModule = FModuleManager::LoadModuleChecked("LauncherServices");
+ ProjectLauncherServicesModule.OnCreateLauncherDelegate.Remove(LauncherCallbackHandle);
+ }
+ }
+}
+
+void FOculusBuildAnalytics::RegisterLauncherCallback()
+{
+ ILauncherServicesModule& ProjectLauncherServicesModule = FModuleManager::LoadModuleChecked("LauncherServices");
+ LauncherCallbackHandle = ProjectLauncherServicesModule.OnCreateLauncherDelegate.AddRaw(this, &FOculusBuildAnalytics::OnLauncherCreated);
+}
+
+void FOculusBuildAnalytics::OnLauncherCreated(ILauncherRef Launcher)
+{
+ // Add callback for when launcher worker is started
+ Launcher->FLauncherWorkerStartedDelegate.AddRaw(this, &FOculusBuildAnalytics::OnLauncherWorkerStarted);
+}
+
+void FOculusBuildAnalytics::OnLauncherWorkerStarted(ILauncherWorkerPtr LauncherWorker, ILauncherProfileRef Profile)
+{
+ TArray Platforms = Profile.Get().GetCookedPlatforms();
+ if (Platforms.Num() == 1)
+ {
+ if (Platforms[0].Equals("Android_ASTC") || Platforms[0].Contains("Windows"))
+ {
+ CurrentBuildStage = UNDEFINED_STAGE;
+ AndroidPackageTime = 0;
+ UATLaunched = false;
+ BuildCompleted = false;
+ CurrentBuildPlatform = Platforms[0];
+ TotalBuildTime = 0;
+ BuildStepCount = 0;
+
+ FOculusXRHMDModule::GetPluginWrapper().SetDeveloperMode(true);
+
+ OutputDirectory = Profile.Get().GetPackageDirectory();
+
+ // Assign callbacks for stages
+ LauncherWorker.Get()->OnStageCompleted().AddRaw(this, &FOculusBuildAnalytics::OnStageCompleted);
+ LauncherWorker.Get()->OnOutputReceived().AddRaw(this, &FOculusBuildAnalytics::OnBuildOutputReceived);
+ LauncherWorker.Get()->OnStageStarted().AddRaw(this, &FOculusBuildAnalytics::OnStageStarted);
+ LauncherWorker.Get()->OnCompleted().AddRaw(this, &FOculusBuildAnalytics::OnCompleted);
+
+ // Get information on what oculus platform we are building for and also the OS platform
+ FString OculusPlatform;
+ if (CurrentBuildPlatform.Equals("Android_ASTC"))
+ {
+ UEnum* OculusMobileDevices = StaticEnum();
+ UAndroidRuntimeSettings* Settings = GetMutableDefault();
+ TArray> TargetOculusDevices = Settings->PackageForOculusMobile;
+ TArray Devices;
+
+ if (TargetOculusDevices.Contains(EOculusMobileDevice::Quest2))
+ {
+ Devices.Add("quest2");
+ }
+ OculusPlatform = FString::Join(Devices, TEXT("_"));
+ }
+ else if (CurrentBuildPlatform.Contains("Windows"))
+ {
+ CurrentBuildPlatform = "Windows";
+ OculusPlatform = "rift";
+ }
+
+ // Count user asset files
+ UserAssetCount = 0;
+ TArray FileNames;
+ IFileManager::Get().FindFilesRecursive(FileNames, *FPaths::ProjectContentDir(), TEXT("*.*"), true, false, false);
+ UserAssetCount = FileNames.Num();
+
+ // Count user script files
+ FGameProjectGenerationModule& GameProjectModule = FModuleManager::LoadModuleChecked(TEXT("GameProjectGeneration"));
+ SourceFileCount = 0;
+ SourceFileDirectorySize = 0;
+ GameProjectModule.Get().GetProjectSourceDirectoryInfo(SourceFileCount, SourceFileDirectorySize);
+
+ // Generate build GUID
+ FGuid guid = FGuid::NewGuid();
+ FOculusXRHMDModule::GetPluginWrapper().AddCustomMetadata("build_guid", TCHAR_TO_ANSI(*guid.ToString()));
+
+ // Send build start event with corresponding metadata
+ FOculusXRHMDModule::GetPluginWrapper().AddCustomMetadata("asset_count", TCHAR_TO_ANSI(*FString::FromInt(UserAssetCount)));
+ FOculusXRHMDModule::GetPluginWrapper().AddCustomMetadata("script_count", TCHAR_TO_ANSI(*FString::FromInt(SourceFileCount)));
+
+ FOculusXRHMDModule::GetPluginWrapper().AddCustomMetadata("target_platform", TCHAR_TO_ANSI(*CurrentBuildPlatform));
+ FOculusXRHMDModule::GetPluginWrapper().AddCustomMetadata("target_oculus_platform", TCHAR_TO_ANSI(*OculusPlatform));
+
+ TArray TaskList;
+ LauncherWorker->GetTasks(TaskList);
+ BuildStepCount = TaskList.Num();
+ }
+ }
+}
+
+void FOculusBuildAnalytics::OnCompleted(bool Succeeded, double TotalTime, int32 ErrorCode)
+{
+ if (!BuildCompleted && Succeeded)
+ {
+ SendBuildCompleteEvent(TotalTime);
+ }
+}
+
+void FOculusBuildAnalytics::OnStageCompleted(const FString& StageName, double Time)
+{
+ if (CurrentBuildStage != UNDEFINED_STAGE)
+ {
+ FString TaskName;
+ switch (CurrentBuildStage)
+ {
+ case COOK_IN_EDITOR_STAGE:
+ TaskName = "build_step_editor_cook";
+ break;
+ case LAUNCH_UAT_STAGE:
+ TaskName = "build_step_launch_uat";
+ break;
+ case COMPILE_STAGE:
+ TaskName = "build_step_compile";
+ break;
+ case COOK_STAGE:
+ TaskName = "build_step_cook";
+ break;
+ case DEPLOY_STAGE:
+ TaskName = "build_step_deploy";
+ break;
+ case PACKAGE_STAGE:
+ TaskName = "build_step_package";
+ break;
+ case RUN_STAGE:
+ return;
+ default:
+ TaskName = "build_step_undefined";
+ break;
+ }
+
+ if (AndroidPackageTime > 0)
+ {
+ Time -= AndroidPackageTime;
+ }
+
+ TotalBuildTime += Time;
+ FOculusXRHMDModule::GetPluginWrapper().SendEvent2(TCHAR_TO_ANSI(*TaskName), TCHAR_TO_ANSI(*FString::SanitizeFloat(Time)), "ovrbuild");
+ }
+}
+
+void FOculusBuildAnalytics::OnStageStarted(const FString& StageName)
+{
+ if (StageName.Equals("Cooking in the editor"))
+ {
+ CurrentBuildStage = COOK_IN_EDITOR_STAGE;
+ }
+ else if (StageName.Equals("Build Task") && CurrentBuildStage == LAUNCH_UAT_STAGE)
+ {
+ CurrentBuildStage = COMPILE_STAGE;
+ }
+ else if (StageName.Equals("Build Task"))
+ {
+ CurrentBuildStage = LAUNCH_UAT_STAGE;
+ }
+ else if (StageName.Equals("Cook Task"))
+ {
+ CurrentBuildStage = COOK_STAGE;
+ }
+ else if (StageName.Equals("Package Task"))
+ {
+ CurrentBuildStage = PACKAGE_STAGE;
+ }
+ else if (StageName.Equals("Deploy Task"))
+ {
+ CurrentBuildStage = DEPLOY_STAGE;
+ }
+ else if (StageName.Equals("Run Task"))
+ {
+ CurrentBuildStage = RUN_STAGE;
+ SendBuildCompleteEvent(TotalBuildTime);
+ BuildCompleted = true;
+ }
+ else
+ {
+ CurrentBuildStage = UNDEFINED_STAGE;
+ }
+}
+
+void FOculusBuildAnalytics::OnBuildOutputReceived(const FString& Message)
+{
+ if (CurrentBuildPlatform.Equals("Android_ASTC") && (CurrentBuildStage == DEPLOY_STAGE || CurrentBuildStage == PACKAGE_STAGE))
+ {
+ if (Message.Contains("BUILD SUCCESSFUL"))
+ {
+ FString Text, Time;
+ Message.Split("in", &Text, &Time);
+
+ if (!Time.IsEmpty())
+ {
+ FString SMinutes, SSeconds;
+ if (Time.Contains("m"))
+ {
+ Time.Split("m", &SMinutes, &SSeconds);
+ }
+ else
+ {
+ SSeconds = Time;
+ }
+
+ int Minutes = FCString::Atoi(*SMinutes);
+ int Seconds = FCString::Atoi(*SSeconds);
+
+ AndroidPackageTime = Minutes * 60 + Seconds;
+
+ FOculusXRHMDModule::GetPluginWrapper().SendEvent2("build_step_gradle_build", TCHAR_TO_ANSI(*FString::SanitizeFloat(AndroidPackageTime)), "ovrbuild");
+ }
+ }
+ }
+}
+
+void FOculusBuildAnalytics::SendBuildCompleteEvent(float TotalTime)
+{
+ if (CurrentBuildPlatform.Equals("Android_ASTC"))
+ {
+ int64 APKTotalSize = 0;
+ TArray FoundAPKs;
+ OutputDirectory = FPaths::ProjectDir() + "Binaries/Android";
+ OutputDirectory = FPaths::ConvertRelativePathToFull(OutputDirectory);
+ IFileManager::Get().FindFiles(FoundAPKs, *FPaths::Combine(OutputDirectory, TEXT("*.apk")), true, false);
+
+ FDateTime LatestTime = FDateTime(0);
+ FString LatestAPK;
+ for (int i = 0; i < FoundAPKs.Num(); i++)
+ {
+ FDateTime APKCreationTime = IFileManager::Get().GetTimeStamp(*FPaths::Combine(OutputDirectory, FoundAPKs[i]));
+ if (APKCreationTime > LatestTime)
+ {
+ LatestTime = APKCreationTime;
+ LatestAPK = FoundAPKs[i];
+ }
+ }
+
+ TArray FoundOBBs;
+ LatestTime = FDateTime(0);
+ FString LatestOBB;
+ IFileManager::Get().FindFiles(FoundOBBs, *FPaths::Combine(OutputDirectory, TEXT("*.obb")), true, false);
+ for (int i = 0; i < FoundOBBs.Num(); i++)
+ {
+ FDateTime OBBCreationTime = IFileManager::Get().GetTimeStamp(*FPaths::Combine(OutputDirectory, FoundOBBs[i]));
+ if (OBBCreationTime > LatestTime)
+ {
+ LatestTime = OBBCreationTime;
+ LatestOBB = FoundOBBs[i];
+ }
+ }
+
+ if (!LatestAPK.IsEmpty())
+ {
+ APKTotalSize += IFileManager::Get().FileSize(*FPaths::Combine(OutputDirectory, LatestAPK));
+ }
+ if (!LatestOBB.IsEmpty())
+ {
+ APKTotalSize += IFileManager::Get().FileSize(*FPaths::Combine(OutputDirectory, LatestOBB));
+ }
+
+ if (APKTotalSize > 0)
+ {
+ FOculusXRHMDModule::GetPluginWrapper().AddCustomMetadata("build_output_size", TCHAR_TO_ANSI(*FString::FromInt(APKTotalSize)));
+ }
+ }
+
+ FOculusXRHMDModule::GetPluginWrapper().AddCustomMetadata("build_step_count", TCHAR_TO_ANSI(*FString::FromInt(BuildStepCount)));
+ FOculusXRHMDModule::GetPluginWrapper().SendEvent2("build_complete", TCHAR_TO_ANSI(*FString::SanitizeFloat(TotalTime)), "ovrbuild");
+}
diff --git a/Plugins/MetaXR/Source/OculusXREditor/Private/OculusXRBuildAnalytics.h b/Plugins/MetaXR/Source/OculusXREditor/Private/OculusXRBuildAnalytics.h
new file mode 100644
index 0000000..06198c6
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXREditor/Private/OculusXRBuildAnalytics.h
@@ -0,0 +1,62 @@
+// @lint-ignore-every LICENSELINT
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "ILauncherServicesModule.h"
+#include "ILauncher.h"
+#include "Modules/ModuleManager.h"
+#include "UObject/Class.h"
+#include "AndroidRuntimeSettings.h"
+#include "OculusXRPluginWrapper.h"
+
+enum EBuildStage
+{
+ UNDEFINED_STAGE,
+ COOK_IN_EDITOR_STAGE,
+ COOK_STAGE,
+ LAUNCH_UAT_STAGE,
+ COMPILE_STAGE,
+ PACKAGE_STAGE,
+ DEPLOY_STAGE,
+ RUN_STAGE,
+};
+
+class FOculusBuildAnalytics
+{
+public:
+ static FOculusBuildAnalytics* GetInstance();
+ static void Shutdown();
+ static bool IsOculusXRHMDAvailable();
+
+ void RegisterLauncherCallback();
+ void OnTelemetryToggled(bool Enabled);
+
+ void OnLauncherCreated(ILauncherRef Launcher);
+ void OnLauncherWorkerStarted(ILauncherWorkerPtr LauncherWorker, ILauncherProfileRef Profile);
+ void OnStageCompleted(const FString& StageName, double Time);
+ void OnStageStarted(const FString& StageName);
+ void OnBuildOutputReceived(const FString& Message);
+ void OnCompleted(bool Succeeded, double TotalTime, int32 ErrorCode);
+ void SendBuildCompleteEvent(float TotalTime);
+
+private:
+ FOculusBuildAnalytics();
+
+ static FOculusBuildAnalytics* instance;
+
+ FDelegateHandle LauncherCallbackHandle;
+
+ float TotalBuildTime;
+ float AndroidPackageTime;
+ bool BuildCompleted;
+ bool UATLaunched;
+ int UserAssetCount;
+ int BuildStepCount;
+ int32 SourceFileCount;
+ int64 SourceFileDirectorySize;
+
+ EBuildStage CurrentBuildStage;
+ FString CurrentBuildPlatform;
+ FString OutputDirectory;
+};
diff --git a/Plugins/MetaXR/Source/OculusXREditor/Private/OculusXREditorModule.cpp b/Plugins/MetaXR/Source/OculusXREditor/Private/OculusXREditorModule.cpp
new file mode 100644
index 0000000..3c076b1
--- /dev/null
+++ b/Plugins/MetaXR/Source/OculusXREditor/Private/OculusXREditorModule.cpp
@@ -0,0 +1,598 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+#include "OculusXREditorModule.h"
+
+#include "AssetToolsModule.h"
+#include "OculusXRToolStyle.h"
+#include "OculusXRToolCommands.h"
+#include "OculusXRPlatformToolWidget.h"
+#include "OculusXRAssetDirectory.h"
+#include "OculusXRHMDRuntimeSettings.h"
+#include "IOculusXRProjectSetupModule.h"
+#include "OculusXRHMDTypes.h"
+#include "LevelEditor.h"
+#include "Modules/ModuleManager.h"
+#include "Widgets/Docking/SDockTab.h"
+#include "Widgets/Input/SButton.h"
+#include "PropertyEditorModule.h"
+#include "DetailLayoutBuilder.h"
+#include "DetailCategoryBuilder.h"
+#include "DetailWidgetRow.h"
+#include "GeneralProjectSettings.h"
+#include "IAssetTools.h"
+#include "Framework/MultiBox/MultiBoxBuilder.h"
+#include "ISettingsModule.h"
+#include "OculusXRPassthroughColorLutAsset.h"
+#include "OculusXRHMDModule.h"
+#include "OculusXRPrivacyNotification.h"
+#include "OculusXRSettingsToggle.h"
+#include "OculusXRTelemetryPrivacySettings.h"
+#include "OculusXRTelemetry.h"
+#include "OculusXRTelemetryEditorEvents.h"
+#include "SExternalImageReference.h"
+#include "AndroidRuntimeSettings.h"
+#include "SourceControlHelpers.h"
+#include "Framework/Notifications/NotificationManager.h"
+#include "Widgets/Notifications/SNotificationList.h"
+#include "Editor/EditorPerformanceSettings.h"
+
+#define LOCTEXT_NAMESPACE "OculusXREditor"
+
+const FName FOculusXREditorModule::OculusPlatToolTabName = FName("OculusXRPlaformTool");
+
+void FOculusXREditorModule::PostLoadCallback()
+{
+ FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor");
+}
+
+void FOculusXREditorModule::StartupModule()
+{
+ bModuleValid = true;
+ RegisterSettings();
+ FOculusAssetDirectory::LoadForCook();
+
+ if (!IsRunningCommandlet())
+ {
+ FOculusToolStyle::Initialize();
+ FOculusToolStyle::ReloadTextures();
+
+ FOculusToolCommands::Register();
+
+ PluginCommands = MakeShareable(new FUICommandList);
+
+ PluginCommands->MapAction(
+ FOculusToolCommands::Get().OpenProjectSetupTool,
+ FExecuteAction::CreateRaw(this, &FOculusXREditorModule::PluginOpenSetupToolWindow),
+ FCanExecuteAction());
+ PluginCommands->MapAction(
+ FOculusToolCommands::Get().OpenPlatWindow,
+ FExecuteAction::CreateRaw(this, &FOculusXREditorModule::PluginOpenPlatWindow),
+ FCanExecuteAction());
+ PluginCommands->MapAction(
+ FOculusToolCommands::Get().ToggleDeploySo,
+ FExecuteAction::CreateLambda([=]() {
+ UOculusXRHMDRuntimeSettings* settings = GetMutableDefault