Skip to content

Commit aabf94d

Browse files
author
Robert Onulak
committed
Adds ability to save spatial meshes directly on the HoloLens. Adds ability to convert Room (.room) to Wavefront (.obj) under the HoloToolkit menu.
1 parent 295ff75 commit aabf94d

File tree

6 files changed

+341
-0
lines changed

6 files changed

+341
-0
lines changed

Assets/HoloToolkit/SpatialMapping/Scripts/RemoteMapping/Editor.meta

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
using System.Collections.Generic;
2+
using System.IO;
3+
using System.Text;
4+
using UnityEditor;
5+
using UnityEngine;
6+
7+
namespace HoloToolkit.Unity
8+
{
9+
public static class RoomMeshExporter
10+
{
11+
private const string ExportDirectoryKey = "ExportDirectory";
12+
private const string ExportDirectoryDefault = "MeshExport";
13+
private const string ExportDialogErrorTitle = "Export Error";
14+
private const string WavefrontFileExtension = ".obj";
15+
16+
public static string ExportDirectory
17+
{
18+
get
19+
{
20+
return EditorPrefs.GetString(ExportDirectoryKey, ExportDirectoryDefault);
21+
}
22+
set
23+
{
24+
if (string.IsNullOrEmpty(value))
25+
{
26+
value = ExportDirectoryDefault;
27+
}
28+
29+
EditorPrefs.SetString(ExportDirectoryKey, value);
30+
}
31+
}
32+
33+
private static bool MakeExportDirectory()
34+
{
35+
try
36+
{
37+
Directory.CreateDirectory(ExportDirectory);
38+
}
39+
catch
40+
{
41+
return false;
42+
}
43+
44+
return true;
45+
}
46+
47+
[MenuItem("HoloToolkit/Export/Export Room (.room) To Wavefront (.obj)...")]
48+
public static void ExportRoomToWavefront()
49+
{
50+
string selectedFile = EditorUtility.OpenFilePanelWithFilters("Select Room File", MeshSaver.MeshFolderName, new string[] { "Room", "room" });
51+
if (string.IsNullOrEmpty(selectedFile))
52+
{
53+
return;
54+
}
55+
56+
string fileName = Path.GetFileNameWithoutExtension(selectedFile);
57+
IEnumerable<Mesh> meshes = null;
58+
try
59+
{
60+
meshes = MeshSaver.Load(fileName);
61+
}
62+
catch
63+
{
64+
// Handling exceptions, and null returned by MeshSaver.Load, by checking if meshes
65+
// is still null below.
66+
}
67+
68+
if (meshes == null)
69+
{
70+
EditorUtility.DisplayDialog(ExportDialogErrorTitle, "Unable to parse selected file.", "Ok");
71+
return;
72+
}
73+
74+
SaveMeshesToWavefront(fileName, meshes);
75+
76+
// Open the location on where the mesh was saved.
77+
System.Diagnostics.Process.Start(ExportDirectory);
78+
}
79+
80+
[MenuItem("HoloToolkit/Export/Export Selection To Wavefront (.obj)")]
81+
public static void ExportSelectionToWavefront()
82+
{
83+
Transform[] selectedTransforms = Selection.transforms;
84+
if (selectedTransforms.Length <= 0)
85+
{
86+
EditorUtility.DisplayDialog(ExportDialogErrorTitle, "Please select GameObject(s) within the scene that you want saved.", "OK");
87+
return;
88+
}
89+
90+
List<MeshFilter> meshFilters = new List<MeshFilter>(selectedTransforms.Length);
91+
for (int i = 0, iLength = selectedTransforms.Length; i < iLength; ++i)
92+
{
93+
meshFilters.AddRange(selectedTransforms[i].GetComponentsInChildren<MeshFilter>());
94+
}
95+
96+
if (meshFilters.Count == 0)
97+
{
98+
EditorUtility.DisplayDialog(ExportDialogErrorTitle, "Nothing selected contains a MeshFilter.", "Ok");
99+
return;
100+
}
101+
102+
SaveMeshFiltersToWavefront("Selection", meshFilters);
103+
104+
// Open the location on where the mesh was saved.
105+
System.Diagnostics.Process.Start(ExportDirectory);
106+
}
107+
108+
/// <summary>
109+
/// Saves meshes without any modifications during serialization.
110+
/// </summary>
111+
/// <param name="fileName">Name of the file, without path and extension.</param>
112+
public static void SaveMeshesToWavefront(string fileName, IEnumerable<Mesh> meshes)
113+
{
114+
if (!MakeExportDirectory())
115+
{
116+
EditorUtility.DisplayDialog(ExportDialogErrorTitle, "Failed to create export directory.", "Ok");
117+
return;
118+
}
119+
120+
string filePath = Path.Combine(ExportDirectory, fileName + WavefrontFileExtension);
121+
using (StreamWriter stream = new StreamWriter(filePath))
122+
{
123+
stream.Write(SerializeMeshes(meshes));
124+
}
125+
}
126+
127+
/// <summary>
128+
/// Transform all vertices and normals of the meshes into world space during serialization.
129+
/// </summary>
130+
/// <param name="fileName">Name of the file, without path and extension.</param>
131+
public static void SaveMeshFiltersToWavefront(string fileName, IEnumerable<MeshFilter> meshes)
132+
{
133+
if (!MakeExportDirectory())
134+
{
135+
EditorUtility.DisplayDialog(ExportDialogErrorTitle, "Failed to create export directory.", "Ok");
136+
return;
137+
}
138+
139+
string filePath = Path.Combine(ExportDirectory, fileName + WavefrontFileExtension);
140+
using (StreamWriter stream = new StreamWriter(filePath))
141+
{
142+
stream.Write(SerializeMeshFilters(meshes));
143+
}
144+
}
145+
146+
private static string SerializeMeshes(IEnumerable<Mesh> meshes)
147+
{
148+
StringWriter stream = new StringWriter();
149+
int offset = 0;
150+
foreach (var mesh in meshes)
151+
{
152+
SerializeMesh(mesh, stream, ref offset);
153+
}
154+
return stream.ToString();
155+
}
156+
157+
private static string SerializeMeshFilters(IEnumerable<MeshFilter> meshes)
158+
{
159+
StringWriter stream = new StringWriter();
160+
int offset = 0;
161+
foreach (var mesh in meshes)
162+
{
163+
SerializeMeshFilter(mesh, stream, ref offset);
164+
}
165+
return stream.ToString();
166+
}
167+
168+
/// <summary>
169+
/// Write single mesh to the stream passed in.
170+
/// </summary>
171+
/// <param name="meshFilter">Mesh to be serialized.</param>
172+
/// <param name="stream">Stream to write the mesh into.</param>
173+
/// <param name="offset">Index offset for handling multiple meshes in a single stream.</param>
174+
private static void SerializeMesh(Mesh mesh, TextWriter stream, ref int offset)
175+
{
176+
// Write vertices to .obj file. Need to make sure the points are transformed so everything is at a single origin.
177+
foreach (Vector3 vertex in mesh.vertices)
178+
{
179+
stream.WriteLine(string.Format("v {0} {1} {2}", -vertex.x, vertex.y, vertex.z));
180+
}
181+
182+
// Write normals. Need to transform the direction.
183+
foreach (Vector3 normal in mesh.normals)
184+
{
185+
stream.WriteLine(string.Format("vn {0} {1} {2}", normal.x, normal.y, normal.z));
186+
}
187+
188+
// Write indices.
189+
for (int s = 0, sLength = mesh.subMeshCount; s < sLength; ++s)
190+
{
191+
int[] indices = mesh.GetTriangles(s);
192+
for (int i = 0, iLength = indices.Length - indices.Length % 3; i < iLength; i += 3)
193+
{
194+
// Format is "vertex index / material index / normal index"
195+
stream.WriteLine(string.Format("f {0}//{0} {1}//{1} {2}//{2}",
196+
indices[i + 0] + 1 + offset,
197+
indices[i + 1] + 1 + offset,
198+
indices[i + 2] + 1 + offset));
199+
}
200+
}
201+
202+
offset += mesh.vertices.Length;
203+
}
204+
205+
/// <summary>
206+
/// Write single, transformed, mesh to the stream passed in.
207+
/// </summary>
208+
/// <param name="meshFilter">Contains the mesh to be transformed and serialized.</param>
209+
/// <param name="stream">Stream to write the transformed mesh into.</param>
210+
/// <param name="offset">Index offset for handling multiple meshes in a single stream.</param>
211+
private static void SerializeMeshFilter(MeshFilter meshFilter, TextWriter stream, ref int offset)
212+
{
213+
Mesh mesh = meshFilter.sharedMesh;
214+
215+
// Write vertices to .obj file. Need to make sure the points are transformed so everything is at a single origin.
216+
foreach (Vector3 vertex in mesh.vertices)
217+
{
218+
Vector3 pos = meshFilter.transform.TransformPoint(vertex);
219+
stream.WriteLine(string.Format("v {0} {1} {2}", -pos.x, pos.y, pos.z));
220+
}
221+
222+
// Write normals. Need to transform the direction.
223+
foreach (Vector3 meshNormal in mesh.normals)
224+
{
225+
Vector3 normal = meshFilter.transform.TransformDirection(meshNormal);
226+
stream.WriteLine(string.Format("vn {0} {1} {2}", normal.x, normal.y, normal.z));
227+
}
228+
229+
// Write indices.
230+
for (int s = 0, sLength = mesh.subMeshCount; s < sLength; ++s)
231+
{
232+
int[] indices = mesh.GetTriangles(s);
233+
for (int i = 0, iLength = indices.Length - indices.Length % 3; i < iLength; i += 3)
234+
{
235+
// Format is "vertex index / material index / normal index"
236+
stream.WriteLine(string.Format("f {0}//{0} {1}//{1} {2}//{2}",
237+
indices[i + 0] + 1 + offset,
238+
indices[i + 1] + 1 + offset,
239+
indices[i + 2] + 1 + offset));
240+
}
241+
}
242+
243+
offset += mesh.vertices.Length;
244+
}
245+
}
246+
}

Assets/HoloToolkit/SpatialMapping/Scripts/RemoteMapping/Editor/RoomMeshExporter.cs.meta

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/HoloToolkit/SpatialMapping/Scripts/RemoteMapping/FileSurfaceObserver.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
using System.Collections.Generic;
55
using UnityEngine;
66

7+
#if UNITY_EDITOR
8+
using UnityEditor;
9+
#endif
10+
711
namespace HoloToolkit.Unity
812
{
913
public class FileSurfaceObserver : SpatialMappingSource
@@ -86,4 +90,21 @@ private void Update()
8690
#endif
8791
}
8892
}
93+
94+
#if UNITY_EDITOR
95+
[CustomEditor(typeof(FileSurfaceObserver))]
96+
public class FileSurfaceObserverEditor : Editor
97+
{
98+
public override void OnInspectorGUI()
99+
{
100+
base.OnInspectorGUI();
101+
102+
// Quick way for the user to get access to the room file location.
103+
if (GUILayout.Button("Open File Location"))
104+
{
105+
System.Diagnostics.Process.Start(MeshSaver.MeshFolderName);
106+
}
107+
}
108+
}
109+
#endif
89110
}

Assets/HoloToolkit/SpatialMapping/Scripts/RemoteMapping/MeshSaver.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.IO;
7+
using System.Linq;
78
using UnityEngine;
89

910
#if !UNITY_EDITOR
@@ -38,6 +39,53 @@ public static string MeshFolderName
3839
}
3940
}
4041

42+
/// <summary>
43+
/// Transform all the points within the mesh filter into world space and return a
44+
/// new mesh with the transformed vertices.
45+
/// </summary>
46+
/// <param name="meshFilter">Mesh filter to transform.</param>
47+
/// <returns>Mesh with vertices transformed to world space.</returns>
48+
public static Mesh TransformPoints(MeshFilter meshFilter)
49+
{
50+
Mesh sharedMesh = meshFilter.sharedMesh;
51+
Transform meshTransform = meshFilter.transform;
52+
53+
Mesh result = new Mesh();
54+
55+
// Transform all vertices into world space.
56+
Vector3[] transformedVertices = new Vector3[sharedMesh.vertexCount];
57+
for (int v = 0, vLength = transformedVertices.Length; v < vLength; ++v)
58+
{
59+
transformedVertices[v] = meshTransform.TransformPoint(sharedMesh.vertices[v]);
60+
}
61+
result.vertices = transformedVertices;
62+
63+
// Copy over all the sub mesh indices into the new mesh.
64+
for (int s = 0, sLength = sharedMesh.subMeshCount; s < sLength; ++s)
65+
{
66+
result.SetIndices(sharedMesh.GetIndices(s), MeshTopology.Triangles, s);
67+
}
68+
69+
return result;
70+
}
71+
72+
/// <summary>
73+
/// Transforms all the mesh vertices into world position before saving to file.
74+
/// </summary>
75+
/// <param name="fileName">Name to give the saved mesh file. Exclude path and extension.</param>
76+
/// <param name="meshes">The collection of Mesh objects to save.</param>
77+
/// <returns>Fully qualified name of the saved mesh file.</returns>
78+
/// <remarks>Determines the save path to use and automatically applies the file extension.</remarks>
79+
public static string TransformPointsAndSave(string fileName, IEnumerable<MeshFilter> meshFilters)
80+
{
81+
if (meshFilters == null)
82+
{
83+
throw new ArgumentNullException("Value of meshFilters cannot be null.");
84+
}
85+
86+
return MeshSaver.Save(fileName, meshFilters.Select<MeshFilter, Mesh>(TransformPoints));
87+
}
88+
4189
/// <summary>
4290
/// Saves the provided meshes to the specified file.
4391
/// </summary>

Assets/HoloToolkit/SpatialMapping/Scripts/SpatialMappingObserver.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,11 @@ private void CleanupMeshes(Mesh visualMesh, Mesh colliderMesh)
225225
}
226226
}
227227

228+
public void SaveSpatialMeshes()
229+
{
230+
MeshSaver.TransformPointsAndSave("SpatialMeshes", GetMeshFilters());
231+
}
232+
228233
private GameObject GetSurfaceObject(int surfaceID, Transform parentObject)
229234
{
230235
//If we have surfaces ready for reuse, use those first

0 commit comments

Comments
 (0)