diff --git a/README.md b/README.md index a2431485de10b63a5d04fe190f6ae56555850056..24c42512f09dbaf373c66e17650e1654a421867f 100644 --- a/README.md +++ b/README.md @@ -49,5 +49,46 @@ The name of the Trackable in the World Storage must correspond to the name of th More information about ARFoundation Mesh Tracking can be found here: https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@5.1/manual/features/object-tracking.html +### Map Trackables ### +#### iOS: ARWorldMap #### +On iOS/ARKit Map Trackables are supported with ARWorlMap feature. Only one Map Trackable per World Graph is supported. + +In ARKit, an ARWorldMap includes ARKit's awareness of the physical space in which the user moves the device that can be serialized into a file to be reloaded later on the same or on another device: +https://developer.apple.com/documentation/arkit/arworldmap + +The .map file can be placed in the Unity persistent data path of the application on the user device with the following name: "ARkitWorlMap.map". Alternatively, the variable keyvalueTags of the Trackable can also contain a parameter with the "url" key providing a link to download the map file. + +By default, the origin of the Map Trackable is the point (0, 0 ,0). If the Map includes an Anchor, you can use it as the origin by setting the name of the Trackable in the World Storage with the TrackableId of the ARAnchor stored in the map. https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@5.1/api/UnityEngine.XR.ARSubsystems.TrackableId.html + +Samples for creating and serializing an ARWorldMap with ARFoundation can be found here: +https://github.com/Unity-Technologies/arfoundation-samples/blob/main/Assets/Scripts/Runtime/ARWorldMapController.cs + +#### Android ARCore Cloud Anchors #### + +On Android/ARcore Map Trackables are supported with Cloud Anchors. + +Cloud Anchors are anchors that are hosted on the ARCore API cloud endpoint. This hosting enables users to share experiences in the same app: see https://developers.google.com/ar/develop/cloud-anchors + +For using ARCore cloud anchors you need to: +* Add the the arcore unity extensions package as a dependency of the Unity project https://github.com/google-ar/arcore-unity-extensions +* Add to the runtime asmdef of this package a reference to Google.XR.ARCoreExtensions.asmdef +* Add a new Script Define Symbol in the Unity project settings: "ETSIARF_ARCORE_EXTENSIONS" +* Then, follow the instructions detailed here https://developers.google.com/ar/develop/unity-arf/cloud-anchors/developer-guide and here https://developers.google.com/ar/develop/authorization?platform=unity-arf to configure the project and your google cloud account + +To use a Map Trackable that corresponds to a Google Cloud Anchor, the name of the Trackable in the World Storage must correspond to the name of Google UUID of the anchor. + +### Geo Trackables ### + +#### iOS: perspectives #### + +On iOS, Geo Trackables are not yet supported with this package. + +A first possibility for adding this support is to adapt the Google GeoSpatial code and then run your iOS application with the ARCore AR backend. + +A second possibility is to add the support for ARKit GeoTracking: https://developer.apple.com/documentation/arkit/arkit_in_ios/content_anchors/tracking_geographic_locations_in_ar +This feature is not implemented yet due to the small number of places supported (see https://developer.apple.com/documentation/arkit/argeotrackingconfiguration) + +#### Android: Google GeoSpatial #### +To come \ No newline at end of file diff --git a/Runtime/Scripts/WorldAnalysisARFoundation.cs b/Runtime/Scripts/WorldAnalysisARFoundation.cs index 28aa10d122ff60d2ee2a76acbbbace007d1198a5..b40662f80caee4c2e797584fb4121c2342ea7ab0 100644 --- a/Runtime/Scripts/WorldAnalysisARFoundation.cs +++ b/Runtime/Scripts/WorldAnalysisARFoundation.cs @@ -89,6 +89,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/WorldAnalysisARFoundationCoroutineHelper.cs b/Runtime/Scripts/WorldAnalysisARFoundationCoroutineHelper.cs index 2aebd0785095b226a7c730b44282c2380cd9a4af..b240bde546727c4f19b6969cf2b3efb8ee7f6d7d 100644 --- a/Runtime/Scripts/WorldAnalysisARFoundationCoroutineHelper.cs +++ b/Runtime/Scripts/WorldAnalysisARFoundationCoroutineHelper.cs @@ -8,11 +8,6 @@ public class WorldAnalysisARFoundationCoroutineHelper : MonoBehaviour private void Awake() { - // If there is an instance, and it's not me, delete myself. - - - Debug.Log("HERE SINGLETON AWAKE"); - if (Instance != null && Instance != this) { Destroy(this); @@ -25,7 +20,6 @@ public class WorldAnalysisARFoundationCoroutineHelper : MonoBehaviour public void StartACoroutine(IEnumerator coroutine) { - Debug.Log("HERE SINGLETON"); StartCoroutine(coroutine); } -} +} \ No newline at end of file diff --git a/Runtime/Scripts/WorldAnalysisARFoundationModuleARCoreAnchor.cs b/Runtime/Scripts/WorldAnalysisARFoundationModuleARCoreAnchor.cs index 1d5fb50f05f97406bf90f10b056ea0dad4abfa3a..69af489726a40c3e368d7a8dd6fe0abd3098d607 100644 --- a/Runtime/Scripts/WorldAnalysisARFoundationModuleARCoreAnchor.cs +++ b/Runtime/Scripts/WorldAnalysisARFoundationModuleARCoreAnchor.cs @@ -41,10 +41,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())) @@ -58,10 +58,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 @@ -134,7 +134,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 0000000000000000000000000000000000000000..5b1ef2042b46b360f4d720e7a9972e07d5ad94c8 --- /dev/null +++ b/Runtime/Scripts/WorldAnalysisARFoundationModuleARKitWorldMap.cs @@ -0,0 +1,238 @@ + +#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) + { + Debug.Log("Anchor found " +trackedAnchor.trackableId.ToString()); + + 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) + { + Debug.Log("Load AR Map from URL"); + LoadWorldMapFromURL(url); + // don't check if url is valid + return true ; + } + else + { + Debug.Log("Load AR Map locally"); + 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) + { + while (ARSession.state == ARSessionState.CheckingAvailability || ARSession.state == ARSessionState.None ||ARSession.state == ARSessionState.SessionInitializing) + { + // wait for ar session to be ready + yield return null ; + } + + ARSession session = Component.FindAnyObjectByType<ARSession>() ; + ARKitSessionSubsystem sessionSubsystem = (ARKitSessionSubsystem)session.subsystem; + if (sessionSubsystem == null) + { + Debug.Log("Cannot load map: no ARKitSessionSubsystem"); + } + else + { + 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 0000000000000000000000000000000000000000..ac4deffed588a7e065ead2b93a49a0a13fcd1f43 --- /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 bba58d8e8bef7cf3644d12d5b5f66bd0c410674b..ab15c6e30490d90212a7ab2c4c703835ebfc7e5d 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 8a0ef7e92cea6e2756409282e48069f6b081646e..6ce3bc4c2e55e76138827f1357e7a7755e35cc7a 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": [],