Skip to content
Snippets Groups Projects
WorldAnalysisARFoundationModuleARKitWorldMap.cs 8.93 KiB
Newer Older
lacoche's avatar
lacoche committed

#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());

lacoche's avatar
lacoche committed
            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");
lacoche's avatar
lacoche committed
            LoadWorldMapFromURL(url);
            // don't check if url is valid
            return true ;
        }
        else 
        {
             Debug.Log("Load AR Map locally");
lacoche's avatar
lacoche committed
            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 ;
        }

lacoche's avatar
lacoche committed
        ARSession session = Component.FindAnyObjectByType<ARSession>() ;
        ARKitSessionSubsystem sessionSubsystem = (ARKitSessionSubsystem)session.subsystem;
            Debug.Log("Cannot load map: no ARKitSessionSubsystem");
            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