Mixedrealitytoolkit-unity: How to Save Understanding Mesh

Created on 28 Aug 2016  ยท  34Comments  ยท  Source: microsoft/MixedRealityToolkit-Unity

I'd like to understand how to save the custom mesh built by the new Spatial Understanding contributed by @jevertt. A sample was supplied by @JeffPack inside the pull request but hopefully we can get it moved here.

HoloLens Legacy (HoloToolkit) Question

Most helpful comment

Possible action: There might be enough community interest (I just saw a forum question about this topic) to warrant creating a Spatial Understanding Save/Load component for the HoloToolkit.

All 34 comments

@jbienzms I can share the code no problem. I am triggering the save after mesh finalization as shown in the example. I then just use a voice command "Save room". This calls the function saveRoom() in my RoomSaver class here.

``` C#
using HoloToolkit.Unity;
using UnityEngine;
using UnityEngine.VR.WSA;
using UnityEngine.VR.WSA.Persistence;
using System.Collections;
using System.Collections.Generic;

public class RoomSaver : MonoBehaviour {

public string fileName; // name of file to store meshes
public string anchorStoreName; // name of world anchor to store for room

List roomMeshFilters;
WorldAnchorStore anchorStore;
int meshCount = 0;

// Use this for initialization
void Start()
{
WorldAnchorStore.GetAsync(AnchorStoreReady);
}

void AnchorStoreReady(WorldAnchorStore store)
{
anchorStore = store;
}

public void saveRoom()
{
// if the anchor store is not ready then we cannot save the room mesh
if (anchorStore == null)
return;

// delete old relevant anchors
string[] anchorIds = anchorStore.GetAllIds();
for (int i = 0; i < anchorIds.Length; i++)
{
    if (anchorIds[i].Contains(anchorStoreName))
    {
        anchorStore.Delete(anchorIds[i]);
    }
}

Debug.Log("Old anchors deleted...");

// get all mesh filters used for spatial mapping meshes
roomMeshFilters = SpatialUnderstanding.Instance.UnderstandingCustomMesh.GetMeshFilters() as List<MeshFilter>;

Debug.Log("Mesh filters fetched...");

// create new list of room meshes for serialization
List<Mesh> roomMeshes = new List<Mesh>();

// cycle through all room mesh filters
foreach (MeshFilter filter in roomMeshFilters)
{
    // increase count of meshes in room
    meshCount++;

    // make mesh name = anchor name + mesh count
    string meshName = anchorStoreName + meshCount.ToString();
    filter.mesh.name = meshName;

    Debug.Log("Mesh " + filter.mesh.name + ": " + filter.transform.position + "\n--- rotation " + filter.transform.localRotation + "\n--- scale: " + filter.transform.localScale);
    // add mesh to room meshes for serialization
    roomMeshes.Add(filter.mesh);

    // save world anchor
    WorldAnchor attachingAnchor = filter.gameObject.GetComponent<WorldAnchor>();
    if (attachingAnchor == null)
    {
        attachingAnchor = filter.gameObject.AddComponent<WorldAnchor>();
        Debug.Log("" + filter.mesh.name + ": Using new anchor...");
    }
    else
    {
        Debug.Log("" + filter.mesh.name + ": Deleting existing anchor...");
        DestroyImmediate(attachingAnchor);
        Debug.Log("" + filter.mesh.name + ": Creating new anchor...");
        attachingAnchor = filter.gameObject.AddComponent<WorldAnchor>();
    }
    if (attachingAnchor.isLocated)
    {
        if (!anchorStore.Save(meshName, attachingAnchor))
            Debug.Log("" + meshName + ": Anchor save failed...");
        else
            Debug.Log("" + meshName + ": Anchor SAVED...");
    }
    else
    {
        attachingAnchor.OnTrackingChanged += AttachingAnchor_OnTrackingChanged;
    }
}

// serialize and save meshes
MeshSaver.Save(fileName, roomMeshes);

}

private void AttachingAnchor_OnTrackingChanged(WorldAnchor self, bool located)
{
if (located)
{
string meshName = self.gameObject.GetComponent().mesh.name;
if (!anchorStore.Save(meshName, self))
Debug.Log("" + meshName + ": Anchor save failed...");
else
Debug.Log("" + meshName + ": Anchor SAVED...");

    self.OnTrackingChanged -= AttachingAnchor_OnTrackingChanged;
}

}
}


Then I load it with my RoomLoader class that I attach to my GameManager. This requires a prefab with the basic components (MeshFilter, MeshRenderer, MeshCollider).

``` C#
using HoloToolkit.Unity;
using UnityEngine;
using UnityEngine.VR.WSA.Persistence;
using System.Collections;
using System.Collections.Generic;

public class RoomLoader : MonoBehaviour {

public GameObject surfaceObject;            // prefab for surface mesh objects
public string fileName;                     // name of file used to store mesh
public string anchorStoreName;              // name of world anchor for room

List<Mesh> roomMeshes;                      // list of meshes saved from spatial mapping
WorldAnchorStore anchorStore;               // store of world anchors

// Use this for initialization
void Start () {
    // get instance of WorldAnchorStore
    WorldAnchorStore.GetAsync(AnchorStoreReady);
}

// Update is called once per frame
void Update () {
}

void AnchorStoreReady(WorldAnchorStore store)
{
    // save instance
    anchorStore = store;

    // load room meshes
    roomMeshes = MeshSaver.Load(fileName) as List<Mesh>;

    foreach (Mesh surface in roomMeshes)
    {
        GameObject obj = Instantiate(surfaceObject) as GameObject;
        obj.GetComponent<MeshFilter>().mesh = surface;
        obj.GetComponent<MeshCollider>().sharedMesh = surface;

        if (!anchorStore.Load(surface.name, obj))
            Debug.Log("WorldAnchor load failed...");

        Debug.Log("Mesh " + surface.name + " Position: " + obj.transform.position + "\n--- Rotation: " + obj.transform.localRotation + "\n--- Scale: " + obj.transform.localScale);
    }

    GameObject.Find("GameManager").GetComponent<GameManager>().RoomLoaded();
}
}

In both classes, the anchor store name needs to be set to the same value. One is for saving the world anchors, the other is for loading them. Also, it is important to note that I am using the Mesh saver but I extended the SimpleMeshSerializer.cs to support saving mesh names. This is critical to associating the saved mesh with the right world anchor. Changed code is as follows:

``` C#
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Collections.Generic;
using SysDiag = System.Diagnostics;
using System.IO;
using UnityEngine;

namespace HoloToolkit.Unity
{
///

/// SimpleMeshSerializer converts a UnityEngine.Mesh object to and from an array of bytes.
/// This class saves minimal mesh data (vertices and triangle indices) in the following format:
/// File header: vertex count (32 bit integer), triangle count (32 bit integer)
/// Vertex list: vertex.x, vertex.y, vertex.z (all 32 bit float)
/// Triangle index list: 32 bit integers
///

public static class SimpleMeshSerializer
{
///

/// The mesh header consists of two 32 bit integers.
///

private static int HeaderSize = sizeof(int) * 2;
///


/// Serializes a list of Mesh objects into a byte array.
///

/// List of Mesh objects to be serialized.
/// Binary representation of the Mesh objects.
public static byte[] Serialize(IEnumerable meshes)
{
byte[] data = null;

    using (MemoryStream stream = new MemoryStream())
    {
        using (BinaryWriter writer = new BinaryWriter(stream))
        {
            foreach (Mesh mesh in meshes)
            {
                WriteMesh(writer, mesh);
            }

            stream.Position = 0;
            data = new byte[stream.Length];
            stream.Read(data, 0, data.Length);
        }
    }

    return data;
}

/// <summary>
/// Deserializes a list of Mesh objects from the provided byte array.
/// </summary>
/// <param name="data">Binary data to be deserialized into a list of Mesh objects.</param>
/// <returns>List of Mesh objects.</returns>
public static IEnumerable<Mesh> Deserialize(byte[] data)
{
    List<Mesh> meshes = new List<Mesh>();

    using (MemoryStream stream = new MemoryStream(data))
    {
        using (BinaryReader reader = new BinaryReader(stream))
        {
            while (reader.BaseStream.Length - reader.BaseStream.Position >= HeaderSize)
            {
                meshes.Add(ReadMesh(reader));
            }
        }
    }

    return meshes;
}

/// <summary>
/// Writes a Mesh object to the data stream.
/// </summary>
/// <param name="writer">BinaryWriter representing the data stream.</param>
/// <param name="mesh">The Mesh object to be written.</param>
private static void WriteMesh(BinaryWriter writer, Mesh mesh)
{
    SysDiag.Debug.Assert(writer != null);

    // Write the mesh data.
    WriteMeshHeader(writer, mesh.name, mesh.vertexCount, mesh.triangles.Length);
    WriteVertices(writer, mesh.vertices);
    WriteTriangleIndicies(writer, mesh.triangles);
}

/// <summary>
/// Reads a single Mesh object from the data stream.
/// </summary>
/// <param name="reader">BinaryReader representing the data stream.</param>
/// <returns>Mesh object read from the stream.</returns>
private static Mesh ReadMesh(BinaryReader reader)
{
    SysDiag.Debug.Assert(reader != null);

    int vertexCount = 0;
    int triangleIndexCount = 0;
    string name;

    // Read the mesh data.
    ReadMeshHeader(reader, out name, out vertexCount, out triangleIndexCount);
    Vector3[] vertices = ReadVertices(reader, vertexCount);
    int[] triangleIndices = ReadTriangleIndicies(reader, triangleIndexCount);

    // Create the mesh.
    Mesh mesh = new Mesh();
    mesh.name = name;
    mesh.vertices = vertices;
    mesh.triangles = triangleIndices;
    // Reconstruct the normals from the vertices and triangles.
    mesh.RecalculateNormals();

    return mesh;
}

/// <summary>
/// Writes a mesh header to the data stream.
/// </summary>
/// <param name="writer">BinaryWriter representing the data stream.</param>
/// <param name="vertexCount">Count of vertices in the mesh.</param>
/// <param name="triangleIndexCount">Count of triangle indices in the mesh.</param>
private static void WriteMeshHeader(BinaryWriter writer, string name, int vertexCount, int triangleIndexCount)
{
    SysDiag.Debug.Assert(writer != null);

    writer.Write(name);
    writer.Write(vertexCount);
    writer.Write(triangleIndexCount);

}

/// <summary>
/// Reads a mesh header from the data stream.
/// </summary>
/// <param name="reader">BinaryReader representing the data stream.</param>
/// <param name="vertexCount">Count of vertices in the mesh.</param>
/// <param name="triangleIndexCount">Count of triangle indices in the mesh.</param>
private static void ReadMeshHeader(BinaryReader reader, out string name, out int vertexCount, out int triangleIndexCount)
{
    SysDiag.Debug.Assert(reader != null);

    name = reader.ReadString();
    vertexCount = reader.ReadInt32();
    triangleIndexCount = reader.ReadInt32();
}

/// <summary>
/// Writes a mesh's vertices to the data stream.
/// </summary>
/// <param name="reader">BinaryReader representing the data stream.</param>
/// <param name="vertices">Array of Vector3 structures representing each vertex.</param>
private static void WriteVertices(BinaryWriter writer, Vector3[] vertices)
{
    SysDiag.Debug.Assert(writer != null);

    foreach (Vector3 vertex in vertices)
    {
        writer.Write(vertex.x);
        writer.Write(vertex.y);
        writer.Write(vertex.z);
    }
}

/// <summary>
/// Reads a mesh's vertices from the data stream.
/// </summary>
/// <param name="reader">BinaryReader representing the data stream.</param>
/// <param name="vertexCount">Count of vertices to read.</param>
/// <returns>Array of Vector3 structures representing the mesh's vertices.</returns>
private static Vector3[] ReadVertices(BinaryReader reader, int vertexCount)
{
    SysDiag.Debug.Assert(reader != null);

    Vector3[] vertices = new Vector3[vertexCount];

    for (int i = 0; i < vertices.Length; i++)
    {
        vertices[i] = new Vector3(reader.ReadSingle(),
                                reader.ReadSingle(),
                                reader.ReadSingle());
    }

    return vertices;
}

/// <summary>
/// Writes the vertex indices that represent a mesh's triangles to the data stream
/// </summary>
/// <param name="writer">BinaryWriter representing the data stream.</param>
/// <param name="triangleIndices">Array of integers that describe how the vertex indices form triangles.</param>
private static void WriteTriangleIndicies(BinaryWriter writer, int[] triangleIndices)
{
    SysDiag.Debug.Assert(writer != null);

    foreach (int index in triangleIndices)
    {
        writer.Write(index);
    }
}

/// <summary>
/// Reads the vertex indices that represent a mesh's triangles from the data stream
/// </summary>
/// <param name="reader">BinaryReader representing the data stream.</param>
/// <param name="triangleIndexCount">Count of indices to read.</param>
/// <returns>Array of integers that describe how the vertex indices form triangles.</returns>
private static int[] ReadTriangleIndicies(BinaryReader reader, int triangleIndexCount)
{
    SysDiag.Debug.Assert(reader != null);

    int[] triangleIndices = new int[triangleIndexCount];

    for (int i = 0; i < triangleIndices.Length; i++)
    {
        triangleIndices[i] = reader.ReadInt32();
    }

    return triangleIndices;
}

}
}
```

Enjoy.

Hey @jeffpack, your post needs a formatting correction pass

Thank you again for sharing this @JeffPack. I was hoping to be able to use the Surface Understanding mesh at design time in Unity. If I'm reading this correctly, it looks like this only works at runtime. Is that right?

The new ObjectSurfaceObserver lets you specify a prefab that can be used at design time as the Surface Mesh. I was hoping to grab the Surface Understanding mesh and use it in concert with this. However, I see you're using MeshSaver which (unfortunately) saves the mesh off in a custom format that Unity doesn't understand. This is why people usually end up going to the Device Portal and saving the room mesh as a .obj file and then bringing that in and using with ObjectSurfaceObserver.

One of the last pieces I still haven't figured out in all of this is how the SpatialMappingSource gets set. ObjectSurfaceObserver, FileSurfaceObserver, SpatialMappingObserver, RemoteMeshTarget and SpatialUnderstandingCustomMesh all inherit from SpatialMappingSource. And I see that SpatialMappingManager has a method called SetSpatialMappingSource that allows you to set the source. But I'm still trying to understand which one gets selected if the scene includes more than one (for example an ObjectSurfaceObserver and a SpatialUnderstandingCustomMesh). I'm not clear if I have to do something to detect when I'm running in the editor and enable / disable them or if that's handled automatically.

Ah! I just answered this for myself. ObjectSurfaceObserver's Start method only contains code when UNITY_EDITOR is defined. Conversely, SpatialUnderstanding.AllowSpatialUnderstanding is only true when UNITY_EDITOR is NOT defined. So they can both be in the scene at the same time.

SpatialMappingObserver MUST be in the scene because SpatialMappingManager requires it through a RequireComponent attribute. It's also set as the default source for the SpatialMappingManager. ObjectSurfaceObserver overwrites the default in its Start handler, but interestingly SpatialUnderstanding doesn't do this. In fact, I can't find anywhere in the sample at all where SpatialUnderstandingCustomMesh gets set as the source for SpatialMappingManager. This seems to be a bug. I will bring this up in the pull request thread.

Nope, I take that back. SpatialMappingManager.Source becomes the source for the processing done by the DLL and eventually stored back into SpatialUnderstandingCustomMesh. Both would not be visible at the same time, but it does appear both are needed.

@jbienzms Sorry I misinterpreted what you were saying. If, by design time, you mean runtime within the Unity editor, then yes you can still use mesh saver, because calling Mesh.Load() returns a list of Mesh components which you can then assign to prefabs like I do in RoomLoader.cs Though the trick here is that you would have to extend SimpleMeshSerializer.cs again so that it could save the position and orientation of that meshes gameObject so as not to depend on the WorldAnchor system, which as I am sure you know, is not available from within the Unity editor. Once scanned and saved you would need to get them from your hololens to your computer.

If, by design time, you mean when Unity is NOT running your application, then you would probably need to grab the meshes after finalization and use something like ObjExporter (found here) on them, although this could be slightly complicated to extend as there is more than one mesh involved. Again, you would then need to get the obj files from your hololens to your computer.

@riverar Sorry! It was such a quick copy and paste job. It should be fixed now.

Is this issue resolved? @jbienzms do you now understand how to save an understanding mesh? @NeerajW I see this was marked as an enhancement, but I'm not sure what the actionable task is on this one.

Possible action: There might be enough community interest (I just saw a forum question about this topic) to warrant creating a Spatial Understanding Save/Load component for the HoloToolkit.

I'd love to see it added directly to the understanding module as base functionality.

@jwittner I agree with @jevertt that this should be added as base functionality to Spatial Understanding. That's what my hope was with marking it as enhancement.

@jevertt @NeerajW @angelaHillier Cool! Maybe we can make a new Issue outlining the desired change?

This would be a wonderful enhancement. Do you want me to change the description or do you want to close this one and open another?

The primary purpose would be to get a much cleaner mesh than what we currently get out of the Device Portal. If we could have the ultimate request I'd love to see an option to get the Spatial Understanding mesh from the Device Portal if an app is running and SU has been initialized. I'm not sure if that's possible though.

I think a new issue makes sense, and you can just reference this one. It'll focus the conversation to reboot I think.

Is there a way to get SpatialUnderstanding "understand" the mesh of the RoomModel property of the ObjectSurfaceObserver? It would be great to have this working, because it would allow us to use Unity Editor to check how a particular mesh stored in OBJ format could be interpreted by the Spatial Understanding module.
So far, I have been unable to do so.

I believe #289 from @Ziugy should fix this.

My work does the first step of saving either the Spatial Mapping or Spatial Understanding meshes. There's still work that will need to be done to enable the "understanding" portion to run on a saved mesh within the Unity Editor.

@mrbobbybobberson, did https://github.com/Microsoft/HoloToolkit-Unity/issues/432 also address this issue?

I believe it addressed it only in a minor way, but thereโ€™s a lot more that can be done for #188.

Sent from my Windows 10 phone

From: Stephen Hodgson
Sent: Wednesday, February 15, 2017 7:19 AM
To: Microsoft/HoloToolkit-Unity
Cc: Robert El-Soudani; Mention
Subject: Re: [Microsoft/HoloToolkit-Unity] How to Save Understanding Mesh(#188)

@mrbobbybobberson, did #290 also address this?
โ€”
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

@matmuze has some great ideas on that.

Sent from my Windows 10 phone

From: [email protected]
Sent: Wednesday, February 15, 2017 12:38 PM
To: Microsoft/HoloToolkit-Unity; Microsoft/HoloToolkit-Unity
Cc: Mention
Subject: RE: [Microsoft/HoloToolkit-Unity] How to Save UnderstandingMesh(#188)

I believe it addressed it only in a minor way, but thereโ€™s a lot more that can be done for #188.

Sent from my Windows 10 phone

From: Stephen Hodgson
Sent: Wednesday, February 15, 2017 7:19 AM
To: Microsoft/HoloToolkit-Unity
Cc: Robert El-Soudani; Mention
Subject: Re: [Microsoft/HoloToolkit-Unity] How to Save Understanding Mesh(#188)

@mrbobbybobberson, did #290 also address this?
โ€”
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

Hi, @jbienzms, I can not run your example. I create roomLoader and roomSaver and modify SimpleMeshSerializer. I save and load correcty the room, but I want to use the SpatialUndestandingDLL query to place the gameobject on wall o sittable, for ex.
var numSpaces = SpatialUnderstandingDllTopology.QueryTopology_FindPositionsSittable(minHeight, maxHeight, minFacingClearance, resultsTopology.Length, resultsTopologyPtr);
How copy the meshes list derived from roomLoader to SpatialUndestandingDLL ?
Thanks

That code above is actually from @JeffPack. Jeff, could you weigh in on @Claudio69's question above? Thanks!

This code doesn't save any of the topological information (the understanding part of the module). It just saves the simplified mesh which is much much cleaner than the native one. Capturing the topological information would require a more in depth method. Perhaps you can pre-query this information and save it under a separate file with it's own world anchors? Not sure...

Hi @JeffPack ,

I am trying to save the meshes created from the SpatialUnderstanding scan on HoloLens as .obj files. I retrieved the meshes as you did using

roomMeshFilters = SpatialUnderstanding.Instance.UnderstandingCustomMesh.GetMeshFilters() as List<MeshFilter>;

Then I updated the MeshSaver.cs to save .obj file as below:

public static string Save(string fileName, List<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 (StreamWriter outputFile = new StreamWriter(OpenFileForWrite(folderName, fileName + fileExtension)))
            {

                int o = 0;
                foreach(Mesh theMesh in meshes)
                {
                    o++;
                    outputFile.WriteLine("o Object." + o);
                    outputFile.Write(MeshToString(theMesh));
                }
            }

            Debug.Log("Mesh file saved.");

            return Path.Combine(folderName, fileName + fileExtension);
        }

public static string MeshToString(Mesh m)
        {
            StringBuilder sb = new StringBuilder();

            foreach (Vector3 v in m.vertices)
            {
                sb.Append(string.Format("v {0} {1} {2}\n", v.x, v.y, v.z));
            }
            sb.Append("\n");
            foreach (Vector3 v in m.normals)
            {
                sb.Append(string.Format("vn {0} {1} {2}\n", v.x, v.y, v.z));
            }
            sb.Append("\n");
            foreach (Vector3 v in m.uv)
            {
                sb.Append(string.Format("vt {0} {1}\n", v.x, v.y));
            }
            for (int material = 0; material < m.subMeshCount; material++)
            {

                int[] triangles = m.GetTriangles(material);
                for (int i = 0; i < triangles.Length; i += 3)
                {
                    sb.Append(string.Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n",
                        triangles[i] + 1, triangles[i + 1] + 1, triangles[i + 2] + 1));
                }
            }
            return sb.ToString();
        }

I used the ObjExporter.cs with some updates as I do not need the material information.

And here is how I call it after calling SpatialUnderstanding.Instance.RequestFinishScan();

List<MeshFilter> filters = SpatialUnderstanding.Instance.UnderstandingCustomMesh.GetMeshFilters() as List<MeshFilter>;
        List<Mesh> meshes = new List<Mesh>();
        foreach (MeshFilter f in filters)
        {
            meshes.Add(f.mesh);
        }
        MeshSaver.Save("SceneMeshesSU" + DateTime.Now.ToString().Replace('/', '-').Replace(":", "-"), meshes);

Now what happens is that it saves a very small part of the scene but not the whole scene scanned. From your comment here https://github.com/Microsoft/HoloToolkit-Unity/pull/187 I think I am calling it before the mesh finalization. So I wanted to know where do you actually call the save function? Where are the meshes finalized?
All I want is to save the scene and then view it using MeshLab or Blender. I do not need to upload it again or anything.

@AliaAlaaElDinAdly Have you been able to find a solution for that ? I have been stuck in a similar situation. My goal is to save the whole mesh as one obj file. I have been using starting scene as example scene in Holotoolkit for Saving spatial mesh. I also used objExporter.cs MeshToString function so that I can convert a mesh into a stream of strings and then POST it as any other text to the cloud. Till now, I have been able to extract individual sub-meshes of scanned surface and put them to cloud. For example My house consists of 42 sub-meshes.

Somebody please help to identify where in the code can I access the whole mesh as once. Once I get it, I shall be able to directly export it as single obj file. I read somewhere worldanchors can help but I am not sure.

Going to close this. It's probably been archived by google by now.

Has the issue been resolved? Can we add a link here?

Actually upon second review probably not. I saw a few PR merges that addressed in some way.

I have setup start and stop spatial understanding through voice commands but got stuck after that. I tried to save the mesh but then instead of creating a different class for Load mesh on a prefab, Can we load the saved .obj of mesh on the ObjectSurfaceObserver in runtime after we finished scanning and saving it? if not, what are the potential issues with it?

Hello,
I try to use @JeffPack codes to save and load the SpatialUnderstanding simplified mesh but I got an error on line 48 of RoomLoader.
Can't get VS2017 to recognize RommLoaded().

Any advice ?

Additionally, I can't find the function to load so how (or maybe when) does it really load the stored mesh please ?
I might have it. Is it loaded automatically when you launch your application the next time ?

EDIT :

Okay, as I understand the tools we have I should use the MeshSaver script (thanks for your work @krishnan-unni btw ๐Ÿ‘). But I do not see the difference between "SaveSpatialUnderstanding" and "SaveSpatialMapping" regarding this script. Obviously SpatialUnderstanding is present in the relevant example but MeshSaver is placed in SpatialMapping, so I tend to understand that it will save mapping mesh and not understanding mesh like I wish.
Is that correct ?

Hi @JeffPack @jbienzms , Could you please look into the issue #1559 regarding MeshSaver in in the toolkit? The saved mesh is all scrambled right now when I load it (though, I am loading the Spatial Mapping mesh not the SU mesh). Works fine inside the unity editor though.

Hello to everyone ,
So @tontonwawa, @sprakhar12, @StephenHodgson , @JeffPack , @jbienz , @NeerajW , @jevertt, on what the most correct decision of implementing that one have any one of you stopped?

In Unity projects it's very uncomfortable to wait that mesh to create itself. I just aimed to re-use that Spatial Understanding Mesh for debugging projects. Waiting every time that mesh to be processed is too long...

I wish, this common problem "no have idea how to save this" will be solved soon by our community

@AliaAlaaElDinAdly came very close to solving the Save Mesh as OBJ issue.
Basically there is a small change I made to his code to fix the

Now what happens is that it saves a very small part of the scene but not the whole scene scanned.

The reason for only seeing "part of the scene" is that the faces are sequential for all objects. If faces for Mesh 1 start from 1 and end at 100 then for Mesh 2, the must start from 101.

A quick fix for this will be adding a static field, faceCount and increment:

        public static int faceCount;
        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)));
            faceCount = 0;
            using (StreamWriter outputFile = new StreamWriter(OpenFileForWrite(folderName, fileName + fileExtension)))
            {

                int o = 0;
                foreach (Mesh theMesh in meshes)
                {
                    o++;
                    outputFile.WriteLine("o Object." + o);
                    outputFile.Write(MeshToString(theMesh, faceCount));
                    outputFile.WriteLine("");
                }
            }

            Debug.Log("Mesh file saved.");

            return Path.Combine(folderName, fileName + fileExtension);
        }


        public static string MeshToString(Mesh m, int lastFaceIndex = 0)
        {
            StringBuilder sb = new StringBuilder();

            foreach (Vector3 v in m.vertices)
            {
                sb.Append(string.Format("v {0} {1} {2}\n", v.x, v.y, v.z));
            }
            sb.Append("\n");
            foreach (Vector3 v in m.normals)
            {
                sb.Append(string.Format("vn {0} {1} {2}\n", v.x, v.y, v.z));
            }
            sb.Append("\n");
            foreach (Vector3 v in m.uv)
            {
                sb.Append(string.Format("vt {0} {1}\n", v.x, v.y));
            }
            for (int material = 0; material < m.subMeshCount; material++)
            {

                int[] triangles = m.GetTriangles(material);
                for (int i = 0; i < triangles.Length; i += 3)
                {
                    faceCount+=3;
                    sb.Append(string.Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n",
                        triangles[i] + 1 + lastFaceIndex, triangles[i + 1] + 1 + lastFaceIndex, triangles[i + 2] + 1 + lastFaceIndex));
                }
            }
            return sb.ToString();
        }

So unfortunately I do not develop for the Hololens anymore so I can't help resolve this issue. My apologies! I hope it all comes together for you soon.

Was this page helpful?
0 / 5 - 0 ratings