Spatial Mapping Persistance ? (OpenXR)

In my application I have set up spatial mapping and everything works fine. I can process on the retrieved map to build out the things I need for my application. But how do I go about making the mapping persistent (OpenXR) ? I want the users to be able to scan the environment only once unless there is any change in the environment. I would also require this map to be shared later on when I make it multiplayer.

There is not a built-in way to have a mesh of a space persist. I have notified our voice of customer team about this, though.

However it is possible to serialize a mesh in unity, save it as a binary file and load it. Here is an example. You can save whatever information is relevant from the mesh.

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;

[Serializable]
public class SerializableMesh
{
    public Vector3[] vertices;
    public int[] triangles;
    // Add other relevant mesh data (normals, UVs, etc.) as needed.
}

public static class MeshSaver
{
    public static void SaveMesh(Mesh mesh, string filename)
    {
        var serializableMesh = new SerializableMesh
        {
            vertices = mesh.vertices,
            triangles = mesh.triangles
            // Add other relevant mesh data here.
        };

        string path = Path.Combine(Application.persistentDataPath, filename);
        using (FileStream fileStream = new FileStream(path, FileMode.Create))
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(fileStream, serializableMesh);
        }
    }

    public static Mesh LoadMesh(string filename)
    {
        string path = Path.Combine(Application.persistentDataPath, filename);
        if (File.Exists(path))
        {
            using (FileStream fileStream = new FileStream(path, FileMode.Open))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                var loadedMesh = (SerializableMesh)formatter.Deserialize(fileStream);

                var mesh = new Mesh
                {
                    vertices = loadedMesh.vertices,
                    triangles = loadedMesh.triangles
                    // Set other relevant mesh data here.
                };
                return mesh;
            }
        }
        else
        {
            Debug.LogWarning($"Mesh file '{filename}' not found.");
            return null;
        }
    }
}

You can then use spatial anchors to align the deserialized mesh with the space.

Let me know if this helps.

Best,

El

Thankyou for the reply !

Spatial anchors is what I had in mind as well.. Serializing the mesh and then storing it and loading it relative to the spatial anchor. But the problem was that when I ran the example code in the openxr spatial anchor storage example I was getting errors on load up (SpaceNotLocatableEXT) and when placing and deleting spatial anchors. I will update this reply with the exact error logs once I test again.

Side question, Would storing and loading the mesh relative to the spatial anchor be accurate ? will the mesh be correctly aligned lets say 30 meters from the spatial anchor ?

Error on Load up :

MLOpenXRQuerySpatialAnchorsStorage in the Magic Leap API failed. Reason: SpaceNotLocatableEXT 
UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
UnityEngine.Logger:LogFormat(LogType, String, Object[])
UnityEngine.Debug:LogErrorFormat(String, Object[])
UnityEngine.XR.MagicLeap.MLPluginLog:ErrorFormat(String, Object[]) (at .\Library\PackageCache\com.magicleap.unitysdk@2.1.0\Runtime\Common\Utils\MLPluginLog.cs:154)
UnityEngine.XR.OpenXR.Utils:DidXrCallSucceed(XrResult, String, Predicate`1, Boolean) (at .\Library\PackageCache\com.magicleap.unitysdk@2.1.0\Runtime\OpenXR\OpenXRUtils.cs:161)
UnityEngine.XR.OpenXR.Features.MagicLeapSupport.MagicLeapSpatialAnchorsStorageFeature:QueryStoredSpatialAnchors(Vector3, Single) (at .\Library\PackageCache\com.magicleap.unitysdk@2.1.0\Runtime\OpenXR\SpatialAnchors\MagicLeapSpatialAnchorsStorageFeature.cs:160)
SpatialAnchorsExample:ReadAnchors(Single) (at D:\Unity Projects\EagleEye\Assets\_Main\Scripts\Sample\SpatialAnchorsExample.cs:145)
<Start>d__10:MoveNext() (at D:\Unity Projects\EagleEye\Assets\_Main\Scripts\Sample\SpatialAnchorsExample.cs:69)
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr) (at \home\bokken\build\output\unity\unity\Runtime\Export\Scripting\Coroutines.cs:17)
 QueryStoredSpatialAnchors failed to send request to query for AnchorMapPositionId list around a specific position.
UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
UnityEngine.Logger:Log(LogType, Object)
UnityEngine.Debug:LogError(Object)
UnityEngine.XR.OpenXR.Features.MagicLeapSupport.MagicLeapSpatialAnchorsStorageFeature:QueryStoredSpatialAnchors(Vector3, Single) (at .\Library\PackageCache\com.magicleap.unitysdk@2.1.0\Runtime\OpenXR\SpatialAnchors\MagicLeapSpatialAnchorsStorageFeature.cs:164)
SpatialAnchorsExample:ReadAnchors(Single) (at D:\Unity Projects\EagleEye\Assets\_Main\Scripts\Sample\SpatialAnchorsExample.cs:145)
<Start>d__10:MoveNext() (at D:\Unity Projects\EagleEye\Assets\_Main\Scripts\Sample\SpatialAnchorsExample.cs:69)
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr) (at \home\bokken\build\output\unity\unity\Runtime\Export\Scripting\Coroutines.cs:17)

[XR] PollFuture() failed with error XR_ERROR_FUTURE_INVALID_EXT

Error without input :

Could not find active control after binding resolution
UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
UnityEngine.Logger:Log(LogType, Object)
UnityEngine.Debug:Assert(Boolean, String)
UnityEngine.InputSystem.InputActionState:RestoreActionStatesAfterReResolvingBindings(UnmanagedMemory, InputControlList`1, Boolean) (at .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\Actions\InputActionState.cs:631)
UnityEngine.InputSystem.InputActionState:FinishBindingResolution(Boolean, UnmanagedMemory, InputControlList`1, Boolean) (at .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\Actions\InputActionState.cs:489)
UnityEngine.InputSystem.InputActionMap:ResolveBindings() (at .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\Actions\InputActionMap.cs:1386)
UnityEngine.InputSystem.InputActionMap:LazyResolveBindings(Boolean) (at .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\Actions\InputActionMap.cs:1213)
UnityEngine.InputSystem.InputActionState:OnDeviceChange(InputDevice, InputDeviceChange) (at .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\Actions\InputActionState.cs:4423)
UnityEngine.InputSystem.InputManager:AddDevice(InputDevice) (at .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\InputManager.cs:1195)
UnityEngine.InputSystem.InputManager:OnNativeDeviceDiscovered(Int32, String) (at .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\InputManager.cs:2314)
UnityEngineInternal.Input.NativeInputSystem:NotifyDeviceDiscovered(Int32, String) (at \home\bokken\build\output\unity\unity\Modules\Input\Private\Input.cs:129)

Error on placing anchor :


 PublishSpatialAnchorsToStorage sent an empty list of AnchorIds.
UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
UnityEngine.Logger:Log(LogType, Object)
UnityEngine.Debug:LogError(Object)
UnityEngine.XR.OpenXR.Features.MagicLeapSupport.MagicLeapSpatialAnchorsStorageFeature:PublishSpatialAnchorsToStorage(List`1, UInt64) (at .\Library\PackageCache\com.magicleap.unitysdk@2.1.0\Runtime\OpenXR\SpatialAnchors\MagicLeapSpatialAnchorsStorageFeature.cs:186)
UnityEngine.XR.OpenXR.Features.MagicLeapSupport.MagicLeapSpatialAnchorsStorageFeature:PublishSpatialAnchorsToStorage(List`1, UInt64) (at .\Library\PackageCache\com.magicleap.unitysdk@2.1.0\Runtime\OpenXR\SpatialAnchors\MagicLeapSpatialAnchorsStorageFeature.cs:247)
SpatialAnchorsExample:CreateAnchor(ARAnchor) (at D:\Unity Projects\EagleEye\Assets\_Main\Scripts\Sample\SpatialAnchorsExample.cs:139)
SpatialAnchorsExample:OnBumper(CallbackContext) (at D:\Unity Projects\EagleEye\Assets\_Main\Scripts\Sample\SpatialAnchorsExample.cs:118)
UnityEngine.InputSystem.Utilities.DelegateHelpers:InvokeCallbacksSafe(CallbackArray`1&, CallbackContext, String, Object) (at .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\Utilities\DelegateHelpers.cs:46)
UnityEngine.InputSystem.InputActionState:CallActionListeners(Int32, InputActionMap, InputActionPhase, CallbackArray`1&, String) (at .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\Actions\InputActionState.cs:2500)
UnityEngine.InputSystem.InputActionState:ChangePhaseOfActionInternal(Int32, TriggerState*, InputActionPhase, TriggerState&) (at .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\Actions\InputActionState.cs:2447)
UnityEngine.InputSystem.InputActionState:ChangePhaseOfAction(InputActionPhase, TriggerState&, InputActionPhase) (at .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\Actions\InputActionState.cs:2357)
UnityEngine.InputSystem.InputActionState:ProcessDefaultInteraction(TriggerState&, Int32) (at .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\Actions\InputActionState.cs:1931)
UnityEngine.InputSystem.InputActionState:ProcessControlStateChange(Int32, Int32, Int32, Double, InputEventPtr) (at .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\Actions\InputActionState.cs:1517)
UnityEngine.InputSystem.InputActionState:UnityEngine.InputSystem.LowLevel.IInputStateChangeMonitor.NotifyControlStateChanged(InputControl, Double, InputEventPtr, Int64) (at .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\Actions\InputActionState.cs:1326)
UnityEngine.InputSystem.InputManager:FireStateChangeNotifications(Int32, Double, InputEvent*) (at .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\InputManagerStateMonitors.cs:380)
UnityEngine.InputSystem.InputManager:UpdateState(InputDevice, InputUpdateType, Void*, UInt32, UInt32, Double, InputEventPtr) (at .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\InputManager.cs:3558)
UnityEngine.InputSystem.InputManager:UpdateState(InputDevice, InputEvent*, InputUpdateType) (at .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\InputManager.cs:3475)
UnityEngine.InputSystem.InputManager:OnUpdate(InputUpdateType, InputEventBuffer&) (at .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\InputManager.cs:3290)
UnityEngine.InputSystem.LowLevel.<>c__DisplayClass7_0:<set_onUpdate>b__0(NativeInputUpdateType, NativeInputEventBuffer*) (at .\Library\PackageCache\com.unity.inputsystem@1.7.0\InputSystem\NativeInputRuntime.cs:65)
UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate(NativeInputUpdateType, IntPtr) (at \home\bokken\build\output\unity\unity\Modules\Input\Private\Input.cs:120)

Before using Spatial anchors, you will need to map your space using the Spaces Application. This app is on your device’s home menu.

Once you map a space, the device will localize into it, and continue to localize and refine it’s position as you move around. The magically assumes the space is rigid, based on your description, you will be able to attach content to a single anchor and it will remain stable.

(Note: Multiple anchors may help when using AR Cloud and mapping very large locations.)

If the device is powered off, it will try to localize into the previously selected space.

You can learn more about the Localization map API here:

Once you are localized you can the. Use the spatial anchor API to make content persist.

We recently released a demo project that demonstrates how to align large meshes to a physical space. The sample uses multiple points to aid with the initial alignment and can also help if the virtual content is slightly larger or smaller than the physical space.

Thankyou for the detailed reply :slight_smile:

I had actually created a local space in the spaces app and localised into it, so I wonder what the issue could have been with the example script. I will delete the local space that I had made earlier and create one again and retry the example script. I will update here once I do so.

And thanks a lot for linking the example git, I will go through it !

Okay so the code in spatial anchor storage example (openXR) is buggy.

  1. The CreateAnchor function was not waiting for the ARAnchors' trackingState to be set to TrackingState.Tracking, it was always TrackingState.None inside the CreateAnchor function and therefore I get the error "PublishSpatialAnchorsToStorage sent an empty list of AnchorIds."
    This was solved by simply making CreateAnchor function a coroutine and waiting for the tracking state to be set to Tracking.

Now I can create new anchors, I can see them inside the spaces app(meaning that they are being published).

  1. On reloading my application, the spatial anchors from the storage are not being retrieved. I have attached the listeners needed to the MagicLeapSpatialAnchorsStorageFeature but the callbacks are not being called. As in the example script, I am calling the ReadAnchor function when the scene is loaded (after waiting for all the subsystems to be loaded). The QueryStoredSpatialAnchors is called within the ReadAnchor function, but the callback, OnQueryComplete is not being called.

  2. The callbacks are not being called for other actions as well like OnPublishComplete.

Am I missing something ? or is the code in the documentation really buggy ?

Ok, so I noticed that the spatial anchor storage documentation was only added in the may 14th documentation. So I checked the release notes of 1.7, but spatial anchor storage was not mentioned. I decided to update the OS anyways (I was on 1.6) . On testing after the update, the app launched and it was the same as before, the callbacks weren't being called.

I hadn't updated the unity sdk to 2.2, so I updated that in package manager. I opened the magic leap hub's package installer to find some updates available for c sdk, unity package, etc. I updated all of them just to be sure. Now when I take a build, the app immediately crashes :confused: I can no longer take any builds and run it. It always crashes back to the home screen immediately after launching the application.

I have uploaded the crash log

Ps : I also tried going back to the previous unity sdk and also downgraded the packages on ml hub back to how it used to be, but even still its crashing...
crashlog.txt (91.3 KB)

Could use some help here :slight_smile:

Hi @shijin,
I will be happy to help you out. Could you please provide me with a little more information. Could you post some screenshots of your scene hierarchy, as well as your permissions, XR plugin management, and OpenXR settings please. Also the modified version of the example just so I can recreate what you are seeing.
Thank you,
-Sidney

Hey @sfernandez
The current problem is that my build crashes.. (after the update to unity sdk 2.2 from 2.1, c sdk to 1.7 from 1.6). OS update from 1.6 to 1.7 on the magic leap headset was done first and the build had no crash issue, it was after the update of the unity package and c sdk that the crashes started. I wouldn't even get to the "Made with Unity "screen before crashing.

The issue with the spatial anchor storage could have stemmed from the outdated unity package or c sdk, but since I can't take a build that doesn't crash anymore, I am unable to say. Even an empty scene build from the same project crashes. ( I haven't tried creating a new project and building to ml yet)

The information you asked for seems to be regarding the spatial anchor storage problem, could you please confirm what all you need for the crashing issue ? (I have already uploaded the crash log in my previous post)

PS : Should I create a new thread for the crashing issue ?

Hi,
I am trying to debug both issues. As for the crash see if doing a "reimport all" fixes the issue, the crash log looked like there was an internal vulkan error. I would also try removing the app from the device before making a new build. :slight_smile:
-Sidney

Okay, I created a new ML project and set it up, I am able to take and run builds without crashing from that project.

I also did a "Reimport All", still crashing.

The things you asked for :
hierarchy

Attached To LocalizationManager :

using UnityEditor;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features.MagicLeapSupport;
using UnityEngine.XR.OpenXR.NativeTypes;

public class LocalizationMapTest : MonoBehaviour
{
    private MagicLeapLocalizationMapFeature localizationMapFeature = null;
    private MagicLeapLocalizationMapFeature.LocalizationMap[] maps;
    [SerializeField]
    private GameObject _anchorManager;
    [SerializeField]
    public InputAction triggerButton;

    private void Start()
    {
        triggerButton.Enable();
        triggerButton.performed += Localize;
        localizationMapFeature = OpenXRSettings.Instance.GetFeature<MagicLeapLocalizationMapFeature>();
        
    }

    void Localize(InputAction.CallbackContext ctx)
    {
        QueryMaps();
        LocalizeIntoMap();

        gameObject.SetActive(false);
    }
    public void QueryMaps()
    {
        if (localizationMapFeature == null || !localizationMapFeature.enabled)
            return;

        localizationMapFeature.GetLocalizationMapsList(out maps);
    }

    public void LocalizeIntoMap()
    {
        if (localizationMapFeature == null || !localizationMapFeature.enabled)
            return;

        if (maps.Length == 0)
        {
            Debug.LogError("Must query available maps first");
            return;
        }


        XrResult result = localizationMapFeature.RequestMapLocalization(maps[0].MapUUID);
        if(result == XrResult.Success)
        {
            _anchorManager.SetActive(true);
        }
    }
}

Attached To SampleSpatialAnchorManager :

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using UnityEngine.XR.Management;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features.MagicLeapSupport;
using UnityEngine.XR.OpenXR.NativeTypes;

public class SpatialAnchorsExample : MonoBehaviour
{
    // Reference to the ARAnchorManager component, used for anchor creation and management.
    [SerializeField] private ARAnchorManager anchorManager;

    // The distance from the user to query the anchors.
    [SerializeField] private float queryRadius;

    // Reference to the XRController that will be used to position the Anchors
    [SerializeField] private Transform controllerTransform;

    // Input actions for capturing position, rotation, menu interaction, and the bumper press.
    [SerializeField]
    public InputAction menuInputAction =
        new InputAction(binding: "<XRController>/menuButton", expectedControlType: "Button");

    [SerializeField]
    public InputAction bumperInputAction =
        new InputAction(binding: "<XRController>/gripButton", expectedControlType: "Button");

    private MagicLeapSpatialAnchorsStorageFeature anchorStorageFeature;

    // Active subsystem used for querying anchor confidence.
    private MLXrAnchorSubsystem activeSubsystem;

    // List of active anchors tracked by the script.
    private List<ARAnchor> activeAnchors = new List<ARAnchor>();

    private struct StoredAnchor
    {
        public ulong AnchorId;
        public string AnchorMapPositionId;
        public ARAnchor AnchorObject;
    }

    private List<StoredAnchor> activeAnchorsStored =
        new List<StoredAnchor>();

    // Coroutine started on MonoBehaviour Start to ensure subsystems are loaded before enabling input actions.
    private IEnumerator Start()
    {
        // Waits until AR subsystems are loaded before proceeding.
        yield return new WaitUntil(AreSubsystemsLoaded);

        // Enabling input actions.
        menuInputAction.Enable();
        bumperInputAction.Enable();

        // Registering input action callbacks.
        menuInputAction.performed += OnMenu;
        bumperInputAction.performed += OnBumper;

        AttachListeners();

        // Look for any existing anchors to load
        ReadAnchors(3);
    }

    // Checks if the Magic Leap Anchor Subsystem is loaded 
    private bool AreSubsystemsLoaded()
    {
        if (XRGeneralSettings.Instance == null || XRGeneralSettings.Instance.Manager == null ||
            XRGeneralSettings.Instance.Manager.activeLoader == null) return false;
        activeSubsystem =
            XRGeneralSettings.Instance.Manager.activeLoader.GetLoadedSubsystem<XRAnchorSubsystem>() as
                MLXrAnchorSubsystem;

        
        return activeSubsystem != null && OpenXRFeaturesEnabled();
    }


    private bool OpenXRFeaturesEnabled()
    {
        anchorStorageFeature = OpenXRSettings.Instance.GetFeature<MagicLeapSpatialAnchorsStorageFeature>();

        if (anchorStorageFeature == null || !anchorStorageFeature.enabled)
        {
            Debug.LogError("The OpenXR localization and/or spatial anchor features are not enabled.");
            return false;
        }
        return true;
    }

    private void AttachListeners()
    {
        // Anchors Created from a list of Map Location Ids from Querying Storage
        anchorStorageFeature.OnCreationCompleteFromStorage += OnAnchorCompletedCreationFromStorage;
        // Publishing a Local Anchor to Storage
        anchorStorageFeature.OnPublishComplete += OnAnchorPublishComplete;
        // Querying Storage for a list of publish Anchors
        anchorStorageFeature.OnQueryComplete += OnAnchorQueryComplete;
        // Deleting a published Anchor from Storage
        anchorStorageFeature.OnDeletedComplete += OnAnchorDeleteComplete;
        // Update a Published Anchor's Expiration time
        anchorStorageFeature.OnUpdateExpirationCompleted += OnAnchorExpireUpdateCompleted;
    }

    // Cleanup of input actions when the GameObject is destroyed.
    void OnDestroy()
    {
        menuInputAction.Dispose();
        bumperInputAction.Dispose();
    }

    // Callback for creating a new anchor when the bumper button is pressed.
    private void OnBumper(InputAction.CallbackContext obj)
    {
        // Reading the current position and rotation from the input actions.
        Pose currentPose = new Pose(controllerTransform.position, controllerTransform.rotation);

        Debug.Log("SpatialAnchorsTest: Bumper hit, creating Anchor at " + currentPose);

        // Instantiating a new anchor at the current pose and adding it to the list of active anchors.
        ARAnchor newAnchor = Instantiate(anchorManager.anchorPrefab, currentPose.position, currentPose.rotation).GetComponent<ARAnchor>();
        activeAnchors.Add(newAnchor);
        CreateAnchor(newAnchor);
        
    }

    // Callback for deleting the most recently added anchor when the menu button is pressed.
    private void OnMenu(InputAction.CallbackContext obj)
    {
        if (activeAnchors.Count > 0)
        {
            Debug.Log("SpatialAnchorsTest: Menu hit, Deleting Anchor at " + activeAnchors[^1].transform.position);

            DeleteAnchor(activeAnchorsStored.First(a => a.AnchorObject == activeAnchors[^1]).AnchorMapPositionId);
            // Removing the last anchor from the scene and the list of active anchors.
            Destroy(activeAnchors[^1].gameObject);

            activeAnchors.RemoveAt(activeAnchors.Count - 1);
        }
    }

    // Uses the anchor storage feature to persist the anchor
    private IEnumerator CreateAnchor(ARAnchor toPublish)
    {
        while (toPublish.trackingState != TrackingState.Tracking)
            yield return null;

        anchorStorageFeature.PublishSpatialAnchorsToStorage(new List<ARAnchor> { toPublish }, 0);
    }

    // Looks for persisted anchors within a certain radius from the current controller position
    private void ReadAnchors(float radius = 1f)
    {
        Debug.Log("Read Anchors");
        anchorStorageFeature.QueryStoredSpatialAnchors(controllerTransform.transform.position, radius);
    }

    // Updates the expiration time on a persisted anchor
    private void UpdateAnchorExpiration(string anchorMapPositionID)
    {
        Debug.Log("update anchor exp.");
        DateTimeOffset dto = new(DateTime.Now, TimeSpan.Zero);
        // Set to expire in 3 seconds
        ulong expireTime = (ulong)dto.ToUnixTimeSeconds() + 3u;

        anchorStorageFeature.UpdateExpirationonStoredSpatialAnchor(new List<string> { anchorMapPositionID }, expireTime);
    }

    // Uses the anchor storage feature to remove a persisted anchor
    private void DeleteAnchor(string anchorMapPositionID)
    {
        anchorStorageFeature.DeleteStoredSpatialAnchor(new List<string> { anchorMapPositionID });
        activeAnchorsStored.Remove(activeAnchorsStored.First(a => a.AnchorMapPositionId == anchorMapPositionID));
    }


    #region persistence callbacks

    // All of the anchor storage features are asynchronous and have callbacks for when the call is complete
    // Importantly the callbacks are the only places that the anchorMapPositionId is available, and it should be 
    // stored via this callback as it is required to interact with persisted anchors

    private void OnAnchorPublishComplete(ulong anchorId, string anchorMapPositionId)
    {
        Debug.Log("Anchor Publish Complete");
        // Save the anchorMapPositionId with the corresponding ARAnchor created by the anchor subsystem
        for (int i = 0; i < activeAnchors.Count; i++)
        {
            // A Helper function to get the ML Anchor ID from the ARAnchor the Anchor Subsystem uses.
            if (anchorId == activeSubsystem.GetAnchorId(activeAnchors[i]))
            {
                // Do any additional Logic here
                activeAnchorsStored.Add(new()
                {
                    AnchorId = anchorId,
                    AnchorMapPositionId = anchorMapPositionId,
                    AnchorObject = activeAnchors[i]
                });

            }
        }
    }

    // This is where all persisted anchors found during a query will be returned.
    private void OnAnchorQueryComplete(List<string> anchorMapPositionIds)
    {
        Debug.Log("Query Complete");

        // Determine which ARAnchors already exist, which have been deleted, and which should be created new
        List<string> alreadyCreated = new List<string>();
        List<string> deleteList = new List<string>();
        foreach (var storedAnchor in activeAnchorsStored)
        {
            if (anchorMapPositionIds.Contains(storedAnchor.AnchorMapPositionId))
            {
                alreadyCreated.Add(storedAnchor.AnchorMapPositionId);
            }
            else
            {
                deleteList.Add(storedAnchor.AnchorMapPositionId);
            }
        }

        if (deleteList.Count > 0)
        {
            bool result = anchorStorageFeature.DeleteStoredSpatialAnchor(deleteList);

            if (!result)
            {
                Debug.LogError("SpatialAnchorsStorageTest: Error deleting Anchors after query.");
            }
        }

        List<string> createStoredAnchors = new List<string>();

        foreach (string storedAnchor in anchorMapPositionIds)
        {
            if (!alreadyCreated.Contains(storedAnchor))
            {
                createStoredAnchors.Add(storedAnchor);
            }
        }

        if (createStoredAnchors.Count > 0)
        {
            bool result = anchorStorageFeature.CreateSpatialAnchorsFromStorage(createStoredAnchors);

            if (!result)
            {
                Debug.LogError("SpatialAnchorsStorageTest: Error creating Anchors from storage Id.");
            }
        }
    }

    // This is where anchors found by a query, that were not already in the scene, and were subsequently created from 
    // storage are instantiated into the unity scene.
    private void OnAnchorCompletedCreationFromStorage(Pose pose, ulong anchorId, string anchorMapPositionId,
        XrResult result)
    {
        Debug.Log("from storage complete.");
        if (result == XrResult.Success)
        {
            Debug.Log("from storage success.");

            StoredAnchor newAnchor;
            newAnchor.AnchorId = anchorId;
            newAnchor.AnchorMapPositionId = anchorMapPositionId;

            ARAnchor newStoredAnchor =
                Instantiate(anchorManager.anchorPrefab, pose.position, pose.rotation).GetComponent<ARAnchor>();

            newAnchor.AnchorObject = newStoredAnchor;

            activeAnchorsStored.Add(newAnchor);
        }
    }

    // This is where a confirmation of deletion will happen and the current scene representation of the anchor
    // can be removed
    private void OnAnchorDeleteComplete(List<string> anchorMapPositionIds)
    {
        Debug.Log("anchor deletion complete.");

        // Once it has successfully deleted from the map, delete the local ArAnchor GameObject to remove it from the subsystem
        int foundindex = 0;
        bool found = false;
        for (int i = 0; i < activeAnchorsStored.Count; i++)
        {
            if (activeAnchorsStored[i].AnchorMapPositionId == anchorMapPositionIds[0])
            {
                found = true;
                foundindex = i;
                Destroy(activeAnchorsStored[i].AnchorObject.gameObject);
                break;
            }
        }

        if (found)
        {
            activeAnchorsStored.RemoveAt(foundindex);
        }
    }

    // This callback lets you know which anchors were updated
    private void OnAnchorExpireUpdateCompleted(List<string> anchorMapPositionIds)
    {
        Debug.Log("Anchor Update Complete hit with " + anchorMapPositionIds.Count + " results.");
    }

    #endregion
}

Just copied over the SpatialAnchorExample.cs code that I posted above in the new test project (doesn't crash in the new project) and took a build, still the same issue, callbacks are not being called. I checked spaces app to verify if the anchors are being stored... and they are.

Thank you,
I will do some testing and get back to you as soon as I can.
-Sidney

1 Like

Ok, I have some answers.
I copied the scripts you have above, and there are 2 issues.

  1. inside of OnBumper the last line reads CreateAnchor(newAnchor); and it should read StartCoroutine(CreateAnchor(newAnchor));
  2. The anchor prefab held by the anchor manager needs to have an ARAnchor component on it. If you are using the default one from the ARFoundation package it does not contain this.

    You must copy the prefab into a folder outside of packages and add on the ARAnchor component

After I made those two changes I had no problem saving and loading the markers.
I hope that helps,
-Sidney

Thanks for the response !

  1. Oh wow, I totally missed changing it to StartCoroutine, my bad.. (weird how I was able to see the anchors created inside the spaces app tho)

  2. Iam using a custom ARAnchor Prefab and it does have an ARAnchor attached to it

I'll change it to startcoroutine and test and get back to you :smiley:

1 Like

Just tested after changing it to startcoroutine and it works ! :smiley: (In the new test project)

The build from the main project just keeps crashing... any ideas why on that front ?

Ps : I am curious why the anchors created were being shown in the spaces application when I was starting the coroutine like a method...

1 Like

@shijin could you start a new thread since I would like to mark this one as solved if you don't mind.

Oh sure.. thanks again :slight_smile:

1 Like