Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to Save Understanding Mesh #188

Closed
jbienzms opened this issue Aug 28, 2016 · 34 comments
Closed

How to Save Understanding Mesh #188

jbienzms opened this issue Aug 28, 2016 · 34 comments
Labels
Legacy (HoloToolkit) Issues specific to the HoloToolkit product Platform - HoloLens - First Gen Issues specific to Microsoft HoloLens Question

Comments

@jbienzms
Copy link
Contributor

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.

@JeffPack
Copy link

JeffPack commented Aug 28, 2016

@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.

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<MeshFilter> 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<MeshFilter>().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).

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:

// 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;
    /// <summary>
    /// Serializes a list of Mesh objects into a byte array.
    /// </summary>
    /// <param name="meshes">List of Mesh objects to be serialized.</param>
    /// <returns>Binary representation of the Mesh objects.</returns>
    public static byte[] Serialize(IEnumerable<Mesh> 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.

@riverar
Copy link

riverar commented Aug 28, 2016

Hey @JeffPack, your post needs a formatting correction pass

@jbienzms
Copy link
Contributor Author

jbienzms commented Aug 28, 2016

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.

@jbienzms
Copy link
Contributor Author

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.

@jbienzms
Copy link
Contributor Author

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.

@JeffPack
Copy link

JeffPack commented Aug 29, 2016

@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.

@JeffPack
Copy link

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

@jwittner
Copy link
Member

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.

@angelaHillier
Copy link

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.

@jevertt
Copy link

jevertt commented Sep 15, 2016

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

@NeerajW
Copy link

NeerajW commented Sep 15, 2016

@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.

@jwittner
Copy link
Member

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

@jbienzms
Copy link
Contributor Author

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.

@jwittner
Copy link
Member

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

@fcchub
Copy link

fcchub commented Sep 20, 2016

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.

@jwittner
Copy link
Member

I believe #289 from @Ziugy should fix this.

@Ziugy
Copy link
Member

Ziugy commented Oct 18, 2016

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.

@StephenHodgson
Copy link
Contributor

StephenHodgson commented Feb 15, 2017

@mrbobbybobberson, did #432 also address this issue?

@mrbobbybobberson
Copy link

mrbobbybobberson commented Feb 15, 2017 via email

@mrbobbybobberson
Copy link

mrbobbybobberson commented Feb 15, 2017 via email

@Claudio69
Copy link

Claudio69 commented Apr 6, 2017

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

@jbienzms
Copy link
Contributor Author

jbienzms commented Apr 6, 2017

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

@JeffPack
Copy link

JeffPack commented Apr 6, 2017

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...

@AliaAlaaElDinAdly
Copy link

AliaAlaaElDinAdly commented Jun 3, 2017

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.

@adityapremi
Copy link

@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.

@StephenHodgson StephenHodgson added the Platform - HoloLens - First Gen Issues specific to Microsoft HoloLens label Oct 18, 2017
@StephenHodgson
Copy link
Contributor

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

@riverar
Copy link

riverar commented Oct 28, 2017

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

@StephenHodgson
Copy link
Contributor

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

@sprakhar12
Copy link

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?

@DamienYannSerres
Copy link

DamienYannSerres commented Jan 5, 2018

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 ?

@krishnan-unni
Copy link

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.

@Go2bad
Copy link

Go2bad commented Jan 9, 2018

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

@amngupta
Copy link

@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();
        }

@JeffPack
Copy link

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.

amngupta added a commit to amngupta/MixedRealityToolkit-Unity that referenced this issue Jan 30, 2018
amngupta added a commit to amngupta/MixedRealityToolkit-Unity that referenced this issue Feb 22, 2018
@david-c-kline david-c-kline reopened this May 21, 2018
@david-c-kline david-c-kline added the Legacy (HoloToolkit) Issues specific to the HoloToolkit product label Aug 6, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Legacy (HoloToolkit) Issues specific to the HoloToolkit product Platform - HoloLens - First Gen Issues specific to Microsoft HoloLens Question
Projects
None yet
Development

No branches or pull requests