using System.Collections.Generic; using System.IO; using System.Text; using UnityEditor; using UnityEngine; using HoloToolkit.Unity; namespace HoloToolkit.Unity.SpatialMapping { public static class RoomMeshExporter { private const string ExportDirectoryKey = "_ExportDirectory"; private const string ExportDirectoryDefault = "MeshExport"; private const string ExportDialogErrorTitle = "Export Error"; private const string WavefrontFileExtension = ".obj"; public static string ExportDirectory { get { return EditorPrefsUtility.GetEditorPref(ExportDirectoryKey, ExportDirectoryDefault); } set { if (string.IsNullOrEmpty(value)) { value = ExportDirectoryDefault; } EditorPrefsUtility.SetEditorPref(ExportDirectoryKey, value); } } private static bool MakeExportDirectory() { try { Directory.CreateDirectory(ExportDirectory); } catch { return false; } return true; } [MenuItem("Mixed Reality Toolkit/Export/Export Room (.room) To Wavefront (.obj)...")] public static void ExportRoomToWavefront() { string selectedFile = EditorUtility.OpenFilePanelWithFilters("Select Room File", MeshSaver.MeshFolderName, new string[] { "Room", "room" }); if (string.IsNullOrEmpty(selectedFile)) { return; } string fileName = Path.GetFileNameWithoutExtension(selectedFile); IEnumerable<Mesh> meshes = null; try { meshes = MeshSaver.Load(fileName); } catch { // Handling exceptions, and null returned by MeshSaver.Load, by checking if meshes // is still null below. } if (meshes == null) { EditorUtility.DisplayDialog(ExportDialogErrorTitle, "Unable to parse selected file.", "Ok"); return; } SaveMeshesToWavefront(fileName, meshes); // Open the location on where the mesh was saved. System.Diagnostics.Process.Start(ExportDirectory); } [MenuItem("Mixed Reality Toolkit/Export/Export Selection To Wavefront (.obj)")] public static void ExportSelectionToWavefront() { Transform[] selectedTransforms = Selection.transforms; if (selectedTransforms.Length <= 0) { EditorUtility.DisplayDialog(ExportDialogErrorTitle, "Please select GameObject(s) within the scene that you want saved.", "OK"); return; } List<MeshFilter> meshFilters = new List<MeshFilter>(selectedTransforms.Length); for (int i = 0, iLength = selectedTransforms.Length; i < iLength; ++i) { meshFilters.AddRange(selectedTransforms[i].GetComponentsInChildren<MeshFilter>()); } if (meshFilters.Count == 0) { EditorUtility.DisplayDialog(ExportDialogErrorTitle, "Nothing selected contains a MeshFilter.", "Ok"); return; } SaveMeshFiltersToWavefront("Selection", meshFilters); // Open the location on where the mesh was saved. System.Diagnostics.Process.Start(ExportDirectory); } /// <summary> /// Saves meshes without any modifications during serialization. /// </summary> /// <param name="fileName">Name of the file, without path and extension.</param> public static void SaveMeshesToWavefront(string fileName, IEnumerable<Mesh> meshes) { if (!MakeExportDirectory()) { EditorUtility.DisplayDialog(ExportDialogErrorTitle, "Failed to create export directory.", "Ok"); return; } string filePath = Path.Combine(ExportDirectory, fileName + WavefrontFileExtension); using (StreamWriter stream = new StreamWriter(filePath)) { stream.Write(SerializeMeshes(meshes)); } } /// <summary> /// Transform all vertices and normals of the meshes into world space during serialization. /// </summary> /// <param name="fileName">Name of the file, without path and extension.</param> public static void SaveMeshFiltersToWavefront(string fileName, IEnumerable<MeshFilter> meshes) { if (!MakeExportDirectory()) { EditorUtility.DisplayDialog(ExportDialogErrorTitle, "Failed to create export directory.", "Ok"); return; } string filePath = Path.Combine(ExportDirectory, fileName + WavefrontFileExtension); using (StreamWriter stream = new StreamWriter(filePath)) { stream.Write(SerializeMeshFilters(meshes)); } } private static string SerializeMeshes(IEnumerable<Mesh> meshes) { StringWriter stream = new StringWriter(); int offset = 0; foreach (var mesh in meshes) { SerializeMesh(mesh, stream, ref offset); } return stream.ToString(); } private static string SerializeMeshFilters(IEnumerable<MeshFilter> meshes) { StringWriter stream = new StringWriter(); int offset = 0; foreach (var mesh in meshes) { SerializeMeshFilter(mesh, stream, ref offset); } return stream.ToString(); } /// <summary> /// Write single mesh to the stream passed in. /// </summary> /// <param name="meshFilter">Mesh to be serialized.</param> /// <param name="stream">Stream to write the mesh into.</param> /// <param name="offset">Index offset for handling multiple meshes in a single stream.</param> private static void SerializeMesh(Mesh mesh, TextWriter stream, ref int offset) { // Write vertices to .obj file. Need to make sure the points are transformed so everything is at a single origin. foreach (Vector3 vertex in mesh.vertices) { stream.WriteLine(string.Format("v {0} {1} {2}", -vertex.x, vertex.y, vertex.z)); } // Write normals. Need to transform the direction. foreach (Vector3 normal in mesh.normals) { stream.WriteLine(string.Format("vn {0} {1} {2}", normal.x, normal.y, normal.z)); } // Write indices. for (int s = 0, sLength = mesh.subMeshCount; s < sLength; ++s) { int[] indices = mesh.GetTriangles(s); for (int i = 0, iLength = indices.Length - indices.Length % 3; i < iLength; i += 3) { // Format is "vertex index / material index / normal index" stream.WriteLine(string.Format("f {0}//{0} {1}//{1} {2}//{2}", indices[i + 2] + 1 + offset, indices[i + 1] + 1 + offset, indices[i + 0] + 1 + offset)); } } offset += mesh.vertices.Length; } /// <summary> /// Write single, transformed, mesh to the stream passed in. /// </summary> /// <param name="meshFilter">Contains the mesh to be transformed and serialized.</param> /// <param name="stream">Stream to write the transformed mesh into.</param> /// <param name="offset">Index offset for handling multiple meshes in a single stream.</param> private static void SerializeMeshFilter(MeshFilter meshFilter, TextWriter stream, ref int offset) { Mesh mesh = meshFilter.sharedMesh; // Write vertices to .obj file. Need to make sure the points are transformed so everything is at a single origin. foreach (Vector3 vertex in mesh.vertices) { Vector3 pos = meshFilter.transform.TransformPoint(vertex); stream.WriteLine(string.Format("v {0} {1} {2}", -pos.x, pos.y, pos.z)); } // Write normals. Need to transform the direction. foreach (Vector3 meshNormal in mesh.normals) { Vector3 normal = meshFilter.transform.TransformDirection(meshNormal); stream.WriteLine(string.Format("vn {0} {1} {2}", normal.x, normal.y, normal.z)); } // Write indices. for (int s = 0, sLength = mesh.subMeshCount; s < sLength; ++s) { int[] indices = mesh.GetTriangles(s); for (int i = 0, iLength = indices.Length - indices.Length % 3; i < iLength; i += 3) { // Format is "vertex index / material index / normal index" stream.WriteLine(string.Format("f {0}//{0} {1}//{1} {2}//{2}", indices[i + 0] + 1 + offset, indices[i + 1] + 1 + offset, indices[i + 2] + 1 + offset)); } } offset += mesh.vertices.Length; } } }