From 086784eb0ea4f4e687493bb1879c34913194f5ca Mon Sep 17 00:00:00 2001 From: Sylvain Buche <sylvain.buche@orange.com> Date: Tue, 16 Jul 2024 15:38:16 +0200 Subject: [PATCH] Implementation of the Geospatial module using ARCore API. Fix a bug in the trackable selection algorithm. Change the way ARAnchorManager is used in the ARCoreAnchors module --- Runtime/ARCoreExtensionsConfig.asset | 2 +- .../Resources/ARFAnchorTrackingPrefab.prefab | 106 ++++++++++ Runtime/Scripts/WorldAnalysisARFoundation.cs | 34 +++- ...dAnalysisARFoundationModuleARCoreAnchor.cs | 10 +- ...rldAnalysisARFoundationModuleGeospatial.cs | 185 ++++++++++++++++++ 5 files changed, 327 insertions(+), 10 deletions(-) create mode 100644 Runtime/Scripts/WorldAnalysisARFoundationModuleGeospatial.cs diff --git a/Runtime/ARCoreExtensionsConfig.asset b/Runtime/ARCoreExtensionsConfig.asset index 858be38..6b08173 100644 --- a/Runtime/ARCoreExtensionsConfig.asset +++ b/Runtime/ARCoreExtensionsConfig.asset @@ -14,5 +14,5 @@ MonoBehaviour: m_EditorClassIdentifier: CloudAnchorMode: 1 SemanticMode: 0 - GeospatialMode: 0 + GeospatialMode: 2 StreetscapeGeometryMode: 0 diff --git a/Runtime/Resources/ARFAnchorTrackingPrefab.prefab b/Runtime/Resources/ARFAnchorTrackingPrefab.prefab index 4b74c4b..1c0ee73 100644 --- a/Runtime/Resources/ARFAnchorTrackingPrefab.prefab +++ b/Runtime/Resources/ARFAnchorTrackingPrefab.prefab @@ -1,5 +1,110 @@ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: +--- !u!1 &919512057412407595 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5663275178603938767} + - component: {fileID: 2250489381470411502} + - component: {fileID: 5243785181569724411} + - component: {fileID: 4959683567476516339} + m_Layer: 0 + m_Name: PurpleSphere + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5663275178603938767 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 919512057412407595} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 20, y: 20, z: 20} + m_ConstrainProportionsScale: 1 + m_Children: [] + m_Father: {fileID: 6911754431728604849} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &2250489381470411502 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 919512057412407595} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &5243785181569724411 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 919512057412407595} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a64202207c2bc6042a40ad749c6eabf1, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!135 &4959683567476516339 +SphereCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 919512057412407595} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Radius: 0.5 + m_Center: {x: 0, y: 0, z: 0} --- !u!1 &6911754431728604848 GameObject: m_ObjectHideFlags: 0 @@ -33,6 +138,7 @@ Transform: - {fileID: 6911754433267149346} - {fileID: 6911754433204531549} - {fileID: 5348077246929615354} + - {fileID: 5663275178603938767} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &6911754432812775762 diff --git a/Runtime/Scripts/WorldAnalysisARFoundation.cs b/Runtime/Scripts/WorldAnalysisARFoundation.cs index 02484a7..28aa10d 100644 --- a/Runtime/Scripts/WorldAnalysisARFoundation.cs +++ b/Runtime/Scripts/WorldAnalysisARFoundation.cs @@ -8,10 +8,11 @@ using System.Collections; using System.Linq; using ETSI.ARF.OpenAPI.WorldStorage; using ETSI.ARF.WorldStorage.REST; +using Unity.XR.CoreUtils; //Implementation of the WorldAnalysis interface public class WorldAnalysisARFoundation : MonoBehaviour, WorldAnalysisInterface -{ +{ /// <summary> /// Dictionnary of susbscription informations for poses, for each item, stored using the UUID of the item (anchor/trackable) /// </summary> @@ -50,6 +51,11 @@ public class WorldAnalysisARFoundation : MonoBehaviour, WorldAnalysisInterface /// </summary> private bool isChekingAvailabilityRunning = false; + /// <summary> + /// Anchor manager + /// </summary> + private ARAnchorManager m_anchorManager; + /// <summary> /// Informations regarding a subscription to a pose /// </summary> @@ -73,10 +79,13 @@ public class WorldAnalysisARFoundation : MonoBehaviour, WorldAnalysisInterface m_relocalizationInformations = new Dictionary<Guid, ETSI.ARF.OpenAPI.WorldStorage.RelocalizationInformation>(); m_computedPoses = new Dictionary<Guid, ETSI.ARF.OpenAPI.WorldAnalysis.Pose>(); m_subscriptionsPoses = new Dictionary<Guid, SubscriptionInfo>(); - + m_trackableModules = new List<WorldAnalysisARFoundationModule>(); WorldAnalysisARFoundationModuleImage imageModule = new WorldAnalysisARFoundationModuleImage(); m_trackableModules.Add(imageModule); + + WorldAnalysisARFoundationModuleGeospatial geospatialModule = new WorldAnalysisARFoundationModuleGeospatial(); + m_trackableModules.Add(geospatialModule); #if UNITY_IOS WorldAnalysisARFoundationModuleMesh meshModule = new WorldAnalysisARFoundationModuleMesh(); m_trackableModules.Add(meshModule); @@ -158,7 +167,9 @@ public class WorldAnalysisARFoundation : MonoBehaviour, WorldAnalysisInterface List<RelocObjects> notTrackedCandidates = new List<RelocObjects>(); //Hierarchy of types - string[] types = { "MESH", "IMAGE_MARKER", "FIDUCIAL_MARKER", "MAP", "OTHER" }; //TODO : GEOPOSE + string[] types = { "MESH", "IMAGE_MARKER", "FIDUCIAL_MARKER", "MAP", "GEOPOSE", "OTHER" }; + + List<Guid> keysToRemove = new List<Guid>(); //We fill in the confidence level lists. foreach (KeyValuePair<Guid, RelocObjects> relocObject in temp) @@ -178,9 +189,18 @@ public class WorldAnalysisARFoundation : MonoBehaviour, WorldAnalysisInterface { notTrackedCandidates.Add(relocObject.Value); } + }else + { + keysToRemove.Add(relocObject.Key); } } + // Remove null trackables from temp + foreach (Guid key in keysToRemove) + { + temp.Remove(key); + } + //uses the types[] array indexes as key, and UUIDs as values List<KeyValuePair<string, Guid>> typesSortedList = new List<KeyValuePair<string, Guid>>(); foreach (var relocObject in temp) @@ -365,7 +385,10 @@ public class WorldAnalysisARFoundation : MonoBehaviour, WorldAnalysisInterface List<ETSI.ARF.OpenAPI.WorldStorage.Capability> capabilities = new List<ETSI.ARF.OpenAPI.WorldStorage.Capability>(); foreach (ETSI.ARF.OpenAPI.WorldAnalysis.Capability capability in currentCapabilities) { - capabilities.Add(WorldAnalysisUnityHelper.ConvertWorldAnalysisCapability(capability)); + if(capability != null) + { + capabilities.Add(WorldAnalysisUnityHelper.ConvertWorldAnalysisCapability(capability)); + } } /// Collect relocalization information @@ -544,7 +567,7 @@ public class WorldAnalysisARFoundation : MonoBehaviour, WorldAnalysisInterface /// <returns></returns> private Guid SelectTrackableWithTypeAndDistance(List<RelocObjects> candidates, List<KeyValuePair<string, Guid>> typesSortedList, UnityEngine.Vector3 cameraTransformPos) { - float bestDistance = 100.0f; //valeur par d�faut = grande distance (� changer) + float bestDistance = 10000000.0f; //default value : long distance (to change ?) Guid selectedTrackableUUID = Guid.Empty; //if only one trackable is candidate @@ -602,7 +625,6 @@ public class WorldAnalysisARFoundation : MonoBehaviour, WorldAnalysisInterface } } } - return selectedTrackableUUID; } diff --git a/Runtime/Scripts/WorldAnalysisARFoundationModuleARCoreAnchor.cs b/Runtime/Scripts/WorldAnalysisARFoundationModuleARCoreAnchor.cs index 9bd2d3e..1d5fb50 100644 --- a/Runtime/Scripts/WorldAnalysisARFoundationModuleARCoreAnchor.cs +++ b/Runtime/Scripts/WorldAnalysisARFoundationModuleARCoreAnchor.cs @@ -30,9 +30,13 @@ public class WorldAnalysisARFoundationModuleARCoreAnchor : WorldAnalysisARFounda public void Initialize() { XROrigin origin = UnityEngine.Object.FindAnyObjectByType<XROrigin>(); - m_anchorManager = origin.gameObject.AddComponent<ARAnchorManager>(); - GameObject anchorPrefab = (GameObject)Resources.Load("ARFAnchorTrackingPrefab"); - m_anchorManager.anchorPrefab = anchorPrefab; + m_anchorManager = origin.gameObject.GetComponent<ARAnchorManager>(); + if (m_anchorManager == null ) + { + m_anchorManager = origin.gameObject.AddComponent<ARAnchorManager>(); + GameObject anchorPrefab = (GameObject)Resources.Load("ARFAnchorTrackingPrefab"); + m_anchorManager.anchorPrefab = anchorPrefab; + } m_anchorManager.anchorsChanged += OnTrackedCloudAnchorChanged; } diff --git a/Runtime/Scripts/WorldAnalysisARFoundationModuleGeospatial.cs b/Runtime/Scripts/WorldAnalysisARFoundationModuleGeospatial.cs new file mode 100644 index 0000000..d57391e --- /dev/null +++ b/Runtime/Scripts/WorldAnalysisARFoundationModuleGeospatial.cs @@ -0,0 +1,185 @@ +#if UNITY_ANDROID && ETSIARF_ARCORE_EXTENSIONS +using System; +using System.Collections.Generic; +using Google.XR.ARCoreExtensions; +using Unity.XR.CoreUtils; +using UnityEngine; +using UnityEngine.XR.ARFoundation; +using UnityEngine.XR.ARSubsystems; +using static WorldAnalysisARFoundationModule; + +public class WorldAnalysisARFoundationModuleGeospatial : WorldAnalysisARFoundationModule +{ + /// <summary> + /// Anchor manager + /// </summary> + private ARAnchorManager m_anchorManager; + + private AREarthManager m_arEarthManager; + + private Dictionary<string, TrackableInfo> m_trackedGeospatialAnchors = new Dictionary<string, TrackableInfo>(); + + private bool geospatialSupported = true; + + /// Correspondance between local trackable and etsi uuid + /// </summary> + private Dictionary<string, string> m_localIdToEtsiId = new Dictionary<string, string>(); + + /// <summary> + /// Initialize ARCore geospatial anchors tracking module + /// </summary> + public void Initialize() + { + XROrigin origin = UnityEngine.Object.FindAnyObjectByType<XROrigin>(); + m_anchorManager = origin.gameObject.GetComponent<ARAnchorManager>(); + if (m_anchorManager == null) + { + m_anchorManager = origin.gameObject.AddComponent<ARAnchorManager>(); + GameObject anchorPrefab = (GameObject)Resources.Load("ARFAnchorTrackingPrefab"); + m_anchorManager.anchorPrefab = anchorPrefab; + } + m_arEarthManager = origin.gameObject.AddComponent<AREarthManager>(); + m_anchorManager.anchorsChanged += OnTrackedGeospatialAnchorChanged; + } + + /// <summary> + /// + /// </summary> + /// <param name="uuid"></param> + /// <returns></returns> + public TrackableInfo GetPoseTrackable(Guid uuid) + { + if (m_trackedGeospatialAnchors.ContainsKey(uuid.ToString())) + { + return m_trackedGeospatialAnchors[uuid.ToString()]; + } + else + { + return null; + } + } + + /// <summary> + /// + /// </summary> + /// <param name="trackable"></param> + /// <returns></returns> + public bool AddTrackable(ETSI.ARF.OpenAPI.WorldStorage.Trackable trackable) + { + if (!geospatialSupported) return false; + + if (trackable.TrackableType != ETSI.ARF.OpenAPI.WorldStorage.TrackableType.GEOPOSE) + { + return false; + } + CreateGeosptialAnchor(trackable); + + return true; + } + + + public async void CreateGeosptialAnchor(ETSI.ARF.OpenAPI.WorldStorage.Trackable trackable) + { + if (geospatialSupported) + { + while (ARSession.state == ARSessionState.CheckingAvailability || ARSession.state == ARSessionState.None || ARSession.state == ARSessionState.SessionInitializing || m_arEarthManager.EarthState != EarthState.Enabled || m_arEarthManager.EarthTrackingState != TrackingState.Tracking) + { + await System.Threading.Tasks.Task.Delay(100); + } + if (m_arEarthManager.IsGeospatialModeSupported(GeospatialMode.Enabled) != FeatureSupported.Supported) + { + Debug.Log("Support : " + m_arEarthManager.IsGeospatialModeSupported(GeospatialMode.Enabled)); + geospatialSupported = false; + } + else + { + double[] values = new double[trackable.TrackablePayload.Length / sizeof(double)]; + + for (int i = 0; i < values.Length; i++) + { + values[i] = BitConverter.ToDouble(trackable.TrackablePayload, i * sizeof(double)); + } + + Quaternion rotation = Quaternion.Euler((float)values[3], (float)values[4], (float)values[5]); + + if (m_arEarthManager.EarthTrackingState == TrackingState.Tracking) + { + var anchor = m_anchorManager.AddAnchor(values[0], values[1], values[2], rotation); + m_localIdToEtsiId.Add(anchor.trackableId.subId2.ToString("X16"), trackable.UUID.ToString()); + } + } + } + } + + + /// <summary> + /// + /// </summary> + /// <returns></returns> + public ETSI.ARF.OpenAPI.WorldAnalysis.Capability GetSupportedCapability() //s'occuper de ça (fonction doc) + { + if (!geospatialSupported) return null; + + ETSI.ARF.OpenAPI.WorldAnalysis.Capability capabilityGeospatialAnchor = new ETSI.ARF.OpenAPI.WorldAnalysis.Capability(); + capabilityGeospatialAnchor.TrackableType = ETSI.ARF.OpenAPI.WorldAnalysis.TrackableType.GEOPOSE; + ETSI.ARF.OpenAPI.WorldAnalysis.EncodingInformationStructure encodingInformation = new ETSI.ARF.OpenAPI.WorldAnalysis.EncodingInformationStructure(); + encodingInformation.DataFormat = ETSI.ARF.OpenAPI.WorldAnalysis.EncodingInformationStructureDataFormat.ARCORE; + encodingInformation.Version = "1.01"; + capabilityGeospatialAnchor.EncodingInformation = encodingInformation; + capabilityGeospatialAnchor.Framerate = 30; // Not particularly specified on ARKit and ARCore + capabilityGeospatialAnchor.Latency = 0; // Not particularly specified on ARKit and ARCore + capabilityGeospatialAnchor.Accuracy = 1; // Not particularly specified on ARKit and ARCore + return capabilityGeospatialAnchor; + } + + /// <summary> + /// + /// </summary> + /// <param name="eventArgs"></param> + private void OnTrackedGeospatialAnchorChanged(ARAnchorsChangedEventArgs eventArgs) + { + foreach (var trackedGeospatialAnchor in eventArgs.updated) + { + Vector3 position = trackedGeospatialAnchor.transform.position; + Quaternion rotation = trackedGeospatialAnchor.transform.rotation; + + TrackableInfo info = new TrackableInfo(); + info.name = trackedGeospatialAnchor.name; + string localId = trackedGeospatialAnchor.trackableId.subId1.ToString("X16");// there must be a better way : does it work every time? + + if (m_localIdToEtsiId.ContainsKey(localId)) + { + info.name = m_localIdToEtsiId[localId]; + } + else + { + Debug.Log("ARCore Geospatial Anchor No correspondance for Local Anchor " + localId); + continue; + } + + if (trackedGeospatialAnchor.trackingState == TrackingState.Tracking) + { + info.state = ETSI.ARF.OpenAPI.WorldAnalysis.PoseEstimationState.OK; + info.confidence = 100; + } + else if (trackedGeospatialAnchor.trackingState == TrackingState.Limited) + { + info.state = ETSI.ARF.OpenAPI.WorldAnalysis.PoseEstimationState.OK; + info.confidence = 50; + } + else + { + info.state = ETSI.ARF.OpenAPI.WorldAnalysis.PoseEstimationState.FAILURE; //ADD NOT_TRACKED ? + info.confidence = 0; + } + info.timeStamp = unchecked((int)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()); + info.position = position; + info.rotation = rotation; + info.trackableType = ETSI.ARF.OpenAPI.WorldAnalysis.TrackableType.GEOPOSE; + + m_trackedGeospatialAnchors[info.name] = info; + } + } +} + +#endif \ No newline at end of file -- GitLab