This project contains both the com.magicleap.solutions.spatialmeshserializer and a pre-configured Unity Project which demonstrate how to capture, save, and reload the runtime mesh generated by the Magic Leap 2. Saving and loading the mesh allows developers to:
-
Scan an environment once and save the resulting spatial meshes.
-
Reload saved meshes in future sessions without rescanning.
-
Use Magic Leap Spaces to recall the saved mesh when localized into a known environment.
-
./Packages/com.magicleap.solutions.spatialmeshserializer/: Contains runtime scripts for serialization, deserialization, and localization. -
Spatial Mesh Serializer Sample: The project includes a sample that can be imported into existing projects from the package manager. The sample includes sample scripts and scene (MeshSample.unity).
- Unity 2022.3.49 or later
- A fully scanned and saved environment using Magic Leap Spaces
- A Magic Leap 2 device that can localize to the environment
- Magic Leap Unity SDK
Developers can use the package be either using the pre-configured project or by importing the com.magicleap.solutions.spatialmeshserializer package into their existing project.
Note: If you are importing the package into an existing project, ensure that the SpaceManager and SpaceImportExport permissions are declared in your project's manifest.
- Clone the repository:
git clone https://github.com/magicleap/SpatialMeshSerializer.git
- Open Unity.
- In the Unity Hub, select Open Project, then choose the cloned repository folder.
- In the Unity Editor, open
Assets/Spatial Mesh Serializer Sample/Scenes/MeshSample.unity. - Build and run the scene on your Magic Leap 2 device.
- After the device localizes into a Space, move around to scan the environment.
- Press the controller’s Trigger to save the mesh.
- Restart the app and press the Trigger again to load the previously saved mesh.
- Open your existing Unity project.
- In Unity, select Window > Package Manager.
- Select the plus (+) icon and choose Add package from git URL.
- Paste:
https://github.com/magicleap/SpatialMeshSerializer.git?path=/Packages/com.magicleap.solutions.spatialmeshserializer - Select Add.
- In the Package Manager, select Magic Leap Spatial Mesh Serializer.
- Under Samples, select Import next to Spatial Mesh Serializer Sample.
- Open the sample scene located in:
Assets/Samples/Magic Leap Spatial Mesh Serializer/<version>/Spatial Mesh Serializer Sample/ - Open
MeshSample.unity, build, and run the scene on your Magic Leap 2 device.
MeshLocalizer handles Magic Leap Space localization. It ensures that saved and loaded meshes are associated with the correct Space and that they appear accurately in the environment.
- Initialization: Call
MeshLocalizer.Initialize()to set up the localization system. - Event handling: Subscribe to
MeshLocalizer.onLocalizationStatusChangedto respond to localization changes. - Space identification: Use
MeshLocalizer.TryGetSpaceUUID()to retrieve the current Space UUID.
using UnityEngine; using MagicLeap.SpatialMeshSerializer; using UnityEngine.XR.Management; using UnityEngine.XR; public class LocalizationExample : MonoBehaviour { private void Start() { StartCoroutine(SetUpLocalization()); } private IEnumerator SetUpLocalization() { // Wait until all OpenXR subsystems are loaded before initializing localization. yield return new WaitUntil(OpenXRSubsystemsAreLoaded); // Subscribe to localization status changes. MeshLocalizer.onLocalizationStatusChanged += OnLocalizationChanged; // Initialize the localizer. // This sets up events and attempts to localize into a known Space. // After localization, you can associate saved meshes with a unique Space ID. MeshLocalizer.Initialize(); } private bool OpenXRSubsystemsAreLoaded() { if (XRGeneralSettings.Instance == null || XRGeneralSettings.Instance.Manager == null || XRGeneralSettings.Instance.Manager.activeLoader == null) { return false; } return XRGeneralSettings.Instance.Manager.activeLoader.GetLoadedSubsystem<XRAnchorSubsystem>() != null; } private void OnDestroy() { // Unsubscribe to avoid memory leaks. MeshLocalizer.onLocalizationStatusChanged -= OnLocalizationChanged; } private void OnLocalizationChanged() { // Check if we have a known space ID now that the device is localized. if (MeshLocalizer.TryGetSpaceUUID(out string spaceId)) { Debug.Log($"Localized into space: {spaceId}. Ready to load or save meshes!"); } else { Debug.Log("Not currently localized into a known space."); } } }MeshSerializer provides methods to save and load meshes tied to a localized Space.
- Saving: Use
SaveCurrentSpaceMeshAsync()to save all meshes from theARMeshManagerto the current Space. - Loading: Use
LoadCurrentSpaceMeshAndInstantiateAsync()to load and instantiate saved meshes from the current Space. - Advanced control: Use methods like
SerializeMeshesAsync()to access intermediate byte arrays for networking or custom workflows.
using UnityEngine; using UnityEngine.XR.ARFoundation; using MagicLeap.SpatialMeshSerializer; public class MeshExample : MonoBehaviour { [SerializeField] private ARMeshManager meshManager; [SerializeField] private GameObject meshPrefab; private async void SaveMeshes() { // Save current space meshes to device storage. await MeshSerializer.SaveCurrentSpaceMeshAsync(meshManager.meshes); Debug.Log("Meshes saved successfully."); } private async void LoadMeshes() { // Load saved meshes for the current Space and instantiate them. GameObject[] loadedMeshes = await MeshSerializer.LoadCurrentSpaceMeshAndInstantiateAsync(meshPrefab); if (loadedMeshes != null && loadedMeshes.Length > 0) { Debug.Log("Meshes loaded successfully."); } else { Debug.Log("No saved meshes found for this space."); } } }For networking or custom workflows, you can work directly with intermediate byte arrays.
using System.Threading.Tasks; using UnityEngine; using UnityEngine.XR.ARFoundation; using Unity.Collections; using MagicLeap.SpatialMeshSerializer; public class AdvancedMeshUsage : MonoBehaviour { public async Task SaveMeshesWithIntermediateArrays(ARMeshManager meshManager) { if (!MeshLocalizer.TryGetSpaceUUID(out string id)) { Debug.Log("Not localized into a Space."); return; } // Serialize meshes into raw byte arrays. NativeArray<byte>[] serializedData = await MeshSerializer.SerializeMeshesAsync(meshManager.meshes); // Save serialized data to disk. await MeshSerializer.SaveMeshesToFileAsync(id, serializedData); // Dispose native arrays to free memory. serializedData.DisposeAll(); } public async Task<GameObject[]> LoadMeshesWithIntermediateArrays(ARMeshManager meshManager) { if (!MeshLocalizer.TryGetSpaceUUID(out string id)) { Debug.Log("Not localized into a Space."); return null; } // Load serialized mesh data from disk. MeshSerializer.LoadedMesh[] loadedMeshes = await MeshSerializer.LoadMeshesAsync(id); if (!loadedMeshes.IsPopulated()) { Debug.Log("No meshes found for this Space."); return null; } // Instantiate meshes in the scene. GameObject[] meshObjects = await MeshSerializer.InstantiateLoadedMeshesAsync(loadedMeshes, meshManager.meshPrefab.gameObject); // Dispose of native arrays after use. loadedMeshes.DisposeAll(); return meshObjects; } }This repository is licensed under the Magic Leap Open Source License. See the LICENSE file for more information.