// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using System; using System.Collections.Generic; using System.IO; using UnityEngine; #if !UNITY_EDITOR && UNITY_WSA using System.Threading.Tasks; using Windows.Storage; using Windows.Storage.Streams; #endif namespace HoloToolkit.Unity.SpatialMapping { /// <summary> /// MeshSaver is a static class containing methods used for saving and loading meshes. /// </summary> public static class MeshSaver { /// <summary> /// The extension given to mesh files. /// </summary> private static string fileExtension = ".room"; /// <summary> /// Read-only property which returns the folder path where mesh files are stored. /// </summary> public static string MeshFolderName { get { #if !UNITY_EDITOR && UNITY_WSA return ApplicationData.Current.RoamingFolder.Path; #else return Application.persistentDataPath; #endif } } /// <summary> /// Transforms all the mesh vertices into world position before saving to file. /// </summary> /// <param name="fileName">Name to give the saved mesh file. Exclude path and extension.</param> /// <param name="meshes">The collection of Mesh objects to save.</param> /// <returns>Fully qualified name of the saved mesh file.</returns> /// <remarks>Determines the save path to use and automatically applies the file extension.</remarks> public static string Save(string fileName, IEnumerable<MeshFilter> meshFilters) { if (string.IsNullOrEmpty(fileName)) { throw new ArgumentException("Must specify a valid fileName."); } if (meshFilters == null) { throw new ArgumentNullException("Value of meshFilters cannot be null."); } // Create the mesh file. String folderName = MeshFolderName; Debug.Log(String.Format("Saving mesh file: {0}", Path.Combine(folderName, fileName + fileExtension))); using (Stream stream = OpenFileForWrite(folderName, fileName + fileExtension)) { // Serialize and write the meshes to the file. byte[] data = SimpleMeshSerializer.Serialize(meshFilters); stream.Write(data, 0, data.Length); stream.Flush(); } Debug.Log("Mesh file saved."); return Path.Combine(folderName, fileName + fileExtension); } /// <summary> /// Saves the provided meshes to the specified file. /// </summary> /// <param name="fileName">Name to give the saved mesh file. Exclude path and extension.</param> /// <param name="meshes">The collection of Mesh objects to save.</param> /// <returns>Fully qualified name of the saved mesh file.</returns> /// <remarks>Determines the save path to use and automatically applies the file extension.</remarks> public static string Save(string fileName, IEnumerable<Mesh> meshes) { if (string.IsNullOrEmpty(fileName)) { throw new ArgumentException("Must specify a valid fileName."); } if (meshes == null) { throw new ArgumentNullException("Value of meshes cannot be null."); } // Create the mesh file. String folderName = MeshFolderName; Debug.Log(String.Format("Saving mesh file: {0}", Path.Combine(folderName, fileName + fileExtension))); using (Stream stream = OpenFileForWrite(folderName, fileName + fileExtension)) { // Serialize and write the meshes to the file. byte[] data = SimpleMeshSerializer.Serialize(meshes); stream.Write(data, 0, data.Length); stream.Flush(); } Debug.Log("Mesh file saved."); return Path.Combine(folderName, fileName + fileExtension); } /// <summary> /// Loads the specified mesh file. /// </summary> /// <param name="fileName">Name of the saved mesh file. Exclude path and extension.</param> /// <returns>Collection of Mesh objects read from the file.</returns> /// <remarks>Determines the path from which to load and automatically applies the file extension.</remarks> public static IList<Mesh> Load(string fileName) { if (string.IsNullOrEmpty(fileName)) { throw new ArgumentException("Must specify a valid fileName."); } List<Mesh> meshes = new List<Mesh>(); // Open the mesh file. String folderName = MeshFolderName; Debug.Log(String.Format("Loading mesh file: {0}", Path.Combine(folderName, fileName + fileExtension))); using (Stream stream = OpenFileForRead(folderName, fileName + fileExtension)) { // Read the file and deserialize the meshes. byte[] data = new byte[stream.Length]; stream.Read(data, 0, data.Length); meshes.AddRange(SimpleMeshSerializer.Deserialize(data)); } Debug.Log("Mesh file loaded."); return meshes; } /// <summary> /// Opens the specified file for reading. /// </summary> /// <param name="folderName">The name of the folder containing the file.</param> /// <param name="fileName">The name of the file, including extension. </param> /// <returns>Stream used for reading the file's data.</returns> private static Stream OpenFileForRead(string folderName, string fileName) { Stream stream = null; #if !UNITY_EDITOR && UNITY_WSA Task<Task> task = Task<Task>.Factory.StartNew( async () => { StorageFolder folder = await StorageFolder.GetFolderFromPathAsync(folderName); StorageFile file = await folder.GetFileAsync(fileName); IRandomAccessStreamWithContentType randomAccessStream = await file.OpenReadAsync(); stream = randomAccessStream.AsStreamForRead(); }); task.Wait(); task.Result.Wait(); #else stream = new FileStream(Path.Combine(folderName, fileName), FileMode.Open, FileAccess.Read); #endif return stream; } /// <summary> /// Opens the specified file for writing. /// </summary> /// <param name="folderName">The name of the folder containing the file.</param> /// <param name="fileName">The name of the file, including extension.</param> /// <returns>Stream used for writing the file's data.</returns> /// <remarks>If the specified file already exists, it will be overwritten.</remarks> private static Stream OpenFileForWrite(string folderName, string fileName) { Stream stream = null; #if !UNITY_EDITOR && UNITY_WSA Task<Task> task = Task<Task>.Factory.StartNew( async () => { StorageFolder folder = await StorageFolder.GetFolderFromPathAsync(folderName); StorageFile file = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting); IRandomAccessStream randomAccessStream = await file.OpenAsync(FileAccessMode.ReadWrite); stream = randomAccessStream.AsStreamForWrite(); }); task.Wait(); task.Result.Wait(); #else stream = new FileStream(Path.Combine(folderName, fileName), FileMode.Create, FileAccess.Write); #endif return stream; } } }