From 900e2250f8d8b9eaa2514fac3818d7d41213f84d Mon Sep 17 00:00:00 2001
From: jlacoche <jeremy.lacoche@orange.com>
Date: Sat, 6 Jul 2024 00:35:24 +0200
Subject: [PATCH] Add module for arkit world map

---
 Runtime/Scripts/WorldAnalysisARFoundation.cs  |   2 +
 ...dAnalysisARFoundationModuleARCoreAnchor.cs |  12 +-
 ...AnalysisARFoundationModuleARKitWorldMap.cs | 221 ++++++++++++++++++
 ...sisARFoundationModuleARKitWorldMap.cs.meta |  11 +
 .../WorldAnalysisARFoundationModuleImage.cs   |   2 +-
 ...i.isg.arf.worldanalysisarfoundation.asmdef |   3 +-
 6 files changed, 243 insertions(+), 8 deletions(-)
 create mode 100644 Runtime/Scripts/WorldAnalysisARFoundationModuleARKitWorldMap.cs
 create mode 100644 Runtime/Scripts/WorldAnalysisARFoundationModuleARKitWorldMap.cs.meta

diff --git a/Runtime/Scripts/WorldAnalysisARFoundation.cs b/Runtime/Scripts/WorldAnalysisARFoundation.cs
index 02484a7..055a72d 100644
--- a/Runtime/Scripts/WorldAnalysisARFoundation.cs
+++ b/Runtime/Scripts/WorldAnalysisARFoundation.cs
@@ -80,6 +80,8 @@ public class WorldAnalysisARFoundation : MonoBehaviour, WorldAnalysisInterface
 #if UNITY_IOS
         WorldAnalysisARFoundationModuleMesh meshModule = new WorldAnalysisARFoundationModuleMesh();
         m_trackableModules.Add(meshModule);
+        WorldAnalysisARFoundationModuleARKitWorldMap worldMapModule = new WorldAnalysisARFoundationModuleARKitWorldMap();
+        m_trackableModules.Add(worldMapModule);
 #endif
 #if ETSIARF_ARCORE_EXTENSIONS
     
diff --git a/Runtime/Scripts/WorldAnalysisARFoundationModuleARCoreAnchor.cs b/Runtime/Scripts/WorldAnalysisARFoundationModuleARCoreAnchor.cs
index 9bd2d3e..f593e41 100644
--- a/Runtime/Scripts/WorldAnalysisARFoundationModuleARCoreAnchor.cs
+++ b/Runtime/Scripts/WorldAnalysisARFoundationModuleARCoreAnchor.cs
@@ -37,10 +37,10 @@ public class WorldAnalysisARFoundationModuleARCoreAnchor : WorldAnalysisARFounda
     }
 
     /// <summary>
-    /// 
+    /// Get the pose of the trackable from its uuid
     /// </summary>
-    /// <param name="uuid"></param>
-    /// <returns></returns>
+    /// <param name="uuid">id of the trackable</param>
+    /// <returns>null or trackableInfo with last updated values</returns>
     public TrackableInfo GetPoseTrackable(Guid uuid)
     {
         if (m_trackedCloudAnchors.ContainsKey(uuid.ToString()))
@@ -54,10 +54,10 @@ public class WorldAnalysisARFoundationModuleARCoreAnchor : WorldAnalysisARFounda
     }
 
     /// <summary>
-    /// 
+    /// Need to be a map
     /// </summary>
     /// <param name="trackable"></param>
-    /// <returns></returns>
+    /// <returns>map or not (does not check is solved)</returns>
     public bool AddTrackable(ETSI.ARF.OpenAPI.WorldStorage.Trackable trackable)
     {
         /// Here : we don't check if the trackable is allready added, AddImageToLibrary does it
@@ -130,7 +130,7 @@ public class WorldAnalysisARFoundationModuleARCoreAnchor : WorldAnalysisARFounda
             info.timeStamp = unchecked((int)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
             info.position = position;
             info.rotation = rotation;
-            info.trackableType = ETSI.ARF.OpenAPI.WorldAnalysis.TrackableType.IMAGE_MARKER;
+            info.trackableType = ETSI.ARF.OpenAPI.WorldAnalysis.TrackableType.MAP;
             m_trackedCloudAnchors[info.name] = info;
         }
     }
diff --git a/Runtime/Scripts/WorldAnalysisARFoundationModuleARKitWorldMap.cs b/Runtime/Scripts/WorldAnalysisARFoundationModuleARKitWorldMap.cs
new file mode 100644
index 0000000..5bb1ea4
--- /dev/null
+++ b/Runtime/Scripts/WorldAnalysisARFoundationModuleARKitWorldMap.cs
@@ -0,0 +1,221 @@
+
+#if UNITY_IOS
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.XR.ARFoundation;
+using UnityEngine.XR.ARSubsystems;
+using UnityEngine.XR.ARKit;
+using System.IO;
+using Unity.XR.CoreUtils;
+using static WorldAnalysisARFoundationModule;
+using ETSI.ARF.OpenAPI.WorldStorage;
+using Unity.Collections;
+
+
+public class WorldAnalysisARFoundationModuleARKitWorldMap : WorldAnalysisARFoundationModule
+{
+    /// <summary>
+    ///  Anchor manager
+    /// </summary>
+    private ARAnchorManager m_anchorManager;
+    ///Has loaded a worl map
+    private bool m_hasAddedMap ;
+    /// Possible trackable id (arfoundation) of an anchor contained in the world map. Take the pose of this anchor if it exists or zero
+    private string m_arfoundationAnchorTrackableId ;
+    /// uuId of the map trackabled 
+    private  Guid   m_trackedUUID ;
+    /// computed and updated pose 
+    private TrackableInfo m_trackedPose ;
+
+    /// <summary>
+    /// Initialize image tracking module
+    /// </summary>
+    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.anchorsChanged += OnTrackedAnchorChanged;
+        m_trackedPose = null ;
+        m_hasAddedMap= false ;
+    }
+
+    /// <summary>
+    ///  found  
+    /// </summary>
+    /// <param name="uuid">name of the mesh trackable</param>
+    public TrackableInfo GetPoseTrackable(Guid uuid)
+    {
+        if (m_trackedPose != null)
+        {
+            if (m_trackedUUID == uuid)
+            {
+                return m_trackedPose ;
+            }
+        }
+        return null; 
+    }
+
+    /// <summary>
+    /// Add a trackable : need to be a map
+    /// </summary>
+    /// <param name="trackable">Image trackable</param>
+    /// <returns>Supported or not</returns>
+    public bool AddTrackable(Trackable trackable)
+    {
+        if (trackable.TrackableType != ETSI.ARF.OpenAPI.WorldStorage.TrackableType.MAP)
+        {
+            return false;
+        }
+        if (m_hasAddedMap) 
+        {
+            Debug.Log("Only one ARKit map can be loaded");
+            return false ;
+        }
+
+        // Check if a map url is provided
+        string url = "" ;
+        if (trackable.KeyvalueTags.ContainsKey("url"))
+        {
+            foreach(string s in trackable.KeyvalueTags["url"])
+            {
+                // first one
+                url = s ;
+                break ;
+            }
+        }
+        bool resul = AddWorldMapToARKit(url); 
+        m_trackedUUID = trackable.UUID ;
+        m_arfoundationAnchorTrackableId = trackable.Name ;
+        m_hasAddedMap = resul ;
+        return resul;
+    }
+
+    /// <summary>
+    ///  Initialize capability object with Mesh tracking
+    /// </summary>
+    public ETSI.ARF.OpenAPI.WorldAnalysis.Capability  GetSupportedCapability()
+    {
+        ETSI.ARF.OpenAPI.WorldAnalysis.Capability capabilityMesh = new ETSI.ARF.OpenAPI.WorldAnalysis.Capability();
+        capabilityMesh.TrackableType = ETSI.ARF.OpenAPI.WorldAnalysis.TrackableType.MAP;
+        ETSI.ARF.OpenAPI.WorldAnalysis.EncodingInformationStructure encodingInformation = new ETSI.ARF.OpenAPI.WorldAnalysis.EncodingInformationStructure();
+        encodingInformation.DataFormat = ETSI.ARF.OpenAPI.WorldAnalysis.EncodingInformationStructureDataFormat.ARKIT;
+        encodingInformation.Version = "1.01";
+        capabilityMesh.EncodingInformation = encodingInformation;
+        capabilityMesh.Framerate = 30; // Not particularly specified on ARKit 
+        capabilityMesh.Latency = 0; // Not particularly specified on ARKit
+        capabilityMesh.Accuracy = 1; // Not particularly specified on ARKit
+        return capabilityMesh ;
+    }
+
+    /// <summary>
+    /// Callback when Unity anchors are updated
+    /// </summary>
+    /// <param name="eventArgs">update values</param>
+    private void OnTrackedAnchorChanged(ARAnchorsChangedEventArgs eventArgs)
+    {
+        foreach (var trackedAnchor in eventArgs.updated)
+        {
+            if (trackedAnchor.trackableId.ToString() == m_arfoundationAnchorTrackableId)
+            {
+                /// look for an anchor with the trackable Id correspond to the ETSI ARF trackable name
+                UpdateTrackableInfoWithPose(trackedAnchor.transform.position,trackedAnchor.transform.rotation);
+                break ;
+            }
+        }
+    }
+
+    /// <summary>
+    /// Add a map to arkitsubsystem : check local file exist or download from given url
+    /// </summary>
+    /// <param name="url">url of a map to download</param>
+    /// <returns>found or not</returns>
+    protected bool AddWorldMapToARKit(string url)
+    {
+        if (url.Length > 0)
+        {
+            LoadWorldMapFromURL(url);
+            // don't check if url is valid
+            return true ;
+        }
+        else 
+        {
+            string localMap = Application.persistentDataPath + "/ARkitWorlMap.map";
+            if (File.Exists(localMap))
+            {
+                var coroutine = CoroutineLoadWorldMap(localMap);
+                WorldAnalysisARFoundationCoroutineHelper.Instance.StartACoroutine(coroutine);
+                return true ;
+            }
+        }
+        // no url and no local map
+        return false ;
+    }
+
+    /// <summary>
+    /// Update pose of the map trackable
+    /// </summary>
+    /// </param name="position"> evaluated position of trackable/param>
+    /// </param name="rotation"> evaluated rotation of the trackable</param>
+    private void UpdateTrackableInfoWithPose(Vector3 position, Quaternion rotation)
+    {
+        m_trackedPose = new TrackableInfo();
+        m_trackedPose.name = m_arfoundationAnchorTrackableId;
+        m_trackedPose.state = ETSI.ARF.OpenAPI.WorldAnalysis.PoseEstimationState.OK; // here : could check the state of the ARKit slam
+        m_trackedPose.confidence = 100;
+        m_trackedPose.timeStamp = unchecked((int)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
+        m_trackedPose.position = position;
+        m_trackedPose.rotation = rotation;
+        m_trackedPose.trackableType = ETSI.ARF.OpenAPI.WorldAnalysis.TrackableType.MAP;
+    }
+
+    /// <summary>
+    /// Load a world map from a url
+    /// </summary>
+    /// </param name="url"> url to the worlmap to download</param>
+    private async void LoadWorldMapFromURL(string url)
+    {
+        Debug.Log("Download WorlMap from url "+ url);
+        KeyValuePair<string , string> downloaded = await WorldAnalysisARFoundationHelper.DownloadFileHTTP(url);
+        var coroutine = CoroutineLoadWorldMap(downloaded.Key);
+        WorldAnalysisARFoundationCoroutineHelper.Instance.StartACoroutine(coroutine);
+    }
+
+    /// <summary>
+    /// Load current map from path
+    /// </summary>
+    /// <param name="mapPath">path of the map</param>
+    /// <returns>coroutine</returns>
+    public IEnumerator CoroutineLoadWorldMap(string mapPath)
+    {
+        ARSession session = Component.FindAnyObjectByType<ARSession>() ;
+        ARKitSessionSubsystem sessionSubsystem = (ARKitSessionSubsystem)session.subsystem;
+        FileStream file;
+        file = File.Open(mapPath, FileMode.Open);
+        const int bytesPerFrame = 1024 * 10;
+        var bytesRemaining = file.Length;
+        var binaryReader = new BinaryReader(file);
+        var allBytes = new List<byte>();
+        while (bytesRemaining > 0)
+        {
+            var bytes = binaryReader.ReadBytes(bytesPerFrame);
+            allBytes.AddRange(bytes);
+            bytesRemaining -= bytesPerFrame;
+            yield return null;
+        }
+
+        var data = new NativeArray<byte>(allBytes.Count, Allocator.Temp);
+        data.CopyFrom(allBytes.ToArray());
+        ARWorldMap worldMap;
+        if (ARWorldMap.TryDeserialize(data, out worldMap))
+        {
+            data.Dispose();
+        }
+        sessionSubsystem.ApplyWorldMap(worldMap);
+        UpdateTrackableInfoWithPose(Vector3.zero, Quaternion.identity); // before trying to find an anchor: default pause is origin of the map
+    }
+}
+#endif
\ No newline at end of file
diff --git a/Runtime/Scripts/WorldAnalysisARFoundationModuleARKitWorldMap.cs.meta b/Runtime/Scripts/WorldAnalysisARFoundationModuleARKitWorldMap.cs.meta
new file mode 100644
index 0000000..ac4deff
--- /dev/null
+++ b/Runtime/Scripts/WorldAnalysisARFoundationModuleARKitWorldMap.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0eb5e51c9f2ae4e8db0c43c245bf7b41
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Runtime/Scripts/WorldAnalysisARFoundationModuleImage.cs b/Runtime/Scripts/WorldAnalysisARFoundationModuleImage.cs
index bba58d8..ab15c6e 100644
--- a/Runtime/Scripts/WorldAnalysisARFoundationModuleImage.cs
+++ b/Runtime/Scripts/WorldAnalysisARFoundationModuleImage.cs
@@ -212,7 +212,7 @@ public class WorldAnalysisARFoundationModuleImage : WorldAnalysisARFoundationMod
     /// </param name="url"> url to the image to download</param>
     /// <param name="fileName">file name</param>
     /// <param name="imageWidthInMeters">image width in meters</param>
-    public async void LoadTextureFromURL(string url, string fileName, float imageWidthInMeters)
+    private async void LoadTextureFromURL(string url, string fileName, float imageWidthInMeters)
     {
         Debug.Log("Download image from url "+ url);
         m_allDownloadedImages.Add(url);
diff --git a/Runtime/etsi.isg.arf.worldanalysisarfoundation.asmdef b/Runtime/etsi.isg.arf.worldanalysisarfoundation.asmdef
index 8a0ef7e..6ce3bc4 100644
--- a/Runtime/etsi.isg.arf.worldanalysisarfoundation.asmdef
+++ b/Runtime/etsi.isg.arf.worldanalysisarfoundation.asmdef
@@ -7,7 +7,8 @@
         "GUID:a9420e37d7990b54abdef6688edbe313",
         "GUID:92703082f92b41ba80f0d6912de66115",
         "GUID:dc960734dc080426fa6612f1c5fe95f3",
-        "GUID:b35d8248e1a04478bb2d70fe76652ddf"
+        "GUID:b35d8248e1a04478bb2d70fe76652ddf",
+        "GUID:dfd0ce20189d48e5b22bb4134b558f5a"
     ],
     "includePlatforms": [],
     "excludePlatforms": [],
-- 
GitLab