microsoft / MixedReality-WorldLockingTools-Unity

Unity tools to provide a stable coordinate system anchored to the physical world.
https://microsoft.github.io/MixedReality-WorldLockingTools-Unity/README.html
MIT License
188 stars 45 forks source link

how to move from Spatialanchors to World locking tools #198

Closed autimator closed 3 years ago

autimator commented 3 years ago

Hello,

After a couple of hours off reading the documentation and trying various sample application on my HoloLens 2 I still don't know how I can best upgrade my code.

The idea is quite simple: mark x locations in the world with a sphere and compute which one is closest to the hololens. Storing and reloading the list when the program is restarted. Previously I just created one anchor/object and cloud than retrieve the list of anchors to find the closest one.

Now I am moving my application to unity 2020.3.17f1 and trying to add the recommended worldlocking tools. I think spacepins are best suited for my application?

But I can't figure out how to add them from code and persist their position, the code below doesn't work.

sphere.AddComponent(); SpacePin test = sphere.GetComponent(); test.SetFrozenPose(test.ExtractModelPose());

I suspect there must be an easy way to do this but I cannot find it in the samples.

fast-slow-still commented 3 years ago

Hi @autimator , you are right, there's a very easy way to do this. I think you didn't see it because it is so easy.

A little background. In the spatial anchor pattern you are used to, you create a spatial anchor to mark a position in the physical world. The coordinates of the spatial anchor will change over time, but whatever the current coordinates of the anchor are (assuming you are close enough to the anchor for tracking to work) are the coordinates corresponding to that point in physical space.

WLT anchors every coordinate in Unity's global space to its corresponding point in physical space. If you measure the coordinates of a physical feature in your environment to be (a,b,c), then later on the coordinates there will still be (a,b,c). If you have persistence enabled, then next session that point in physical space will still have coordinates (a,b,c).

Okay, on to your problem. To persist the locations in the world, you previously had to create and save and load a spatial anchor for each sphere. But WLT guarantees that the coordinates of that location in the world will always be the same. So you only need to save and load the position of the sphere. No individual anchors are needed. No SpacePins are needed. (SpacePins have a different job, but it doesn't sound like you need them.)

A couple of tips:

  1. Make sure you are looking at the global position of the camera (not the local position).
  2. It sounds like you want to enable automatic persistence of WLT, make sure Auto Save and Auto Load are checked in the World Locking Context. image

Let me know if you run into trouble with that, or still have questions. I'm not sure I explained it very well, or I might not have understood what you are trying to do.

autimator commented 3 years ago

Thank you for the elaborate answer @fast-slow-still, You right. Storing the locations and loading them again sounds way too simple, but it works. For others who want to do something similar:

private List<GameObject> spheresList;
public string MemoryFileName = "Markers.txt";

    public void markLocation()
    {
        Vector3 hololensPosition = Camera.main.transform.position;
        GameObject sphere = createSphere(System.DateTime.Now.ToString(), Color.red);
        sphere.transform.position = hololensPosition;
        lock (spheresList)
        {
            spheresList.Add(sphere);
            saveLocations();
        }
    }

public GameObject createSphere(string name, Color? col)
    {
        float scale = 0.3F; //scale of the sphere
        if (!col.HasValue)
        { col = Color.red; }

        GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
        sphere.transform.localScale = new Vector3(scale, scale, scale);
        sphere.GetComponent<MeshRenderer>().material.color = (Color)col;
        sphere.name = name;
        return sphere;
    }

    //Read a file with locations from disk
    private void readLocations()
    {
        spheresList = new List<GameObject>();
        if (System.IO.File.Exists(MemoryFileName))
        {
            using (StreamReader reader = new StreamReader(MemoryFileName))
            {
                string location = reader.ReadLine();
                GameObject sphere = createSphere("marker", Color.red);
                sphere.transform.position = StringToVector3(location);
                spheresList.Add(sphere);
            }
        }
    }

    private void saveLocations()
    {
        using (StreamWriter writer = new StreamWriter(MemoryFileName))
        {
            foreach (GameObject go in spheresList)
            {
                string location = go.transform.position.ToString();
                writer.WriteLine(location);
            }
        }
    }

    private static Vector3 StringToVector3(string sVector)
    {
        // Remove the parentheses
        if (sVector.StartsWith("(") && sVector.EndsWith(")"))
        {
            sVector = sVector.Substring(1, sVector.Length - 2);
        }
        // split the items
        string[] sArray = sVector.Split(',');
        // store as a Vector3
        Vector3 result = new Vector3(
            float.Parse(sArray[0]),
            float.Parse(sArray[1]),
            float.Parse(sArray[2]));
        return result;
    }
fast-slow-still commented 3 years ago

Awesome! Thanks for sharing that code!!!

FelixWeichselgartner commented 2 years ago

@autimator I have so comments on the code that you posted.

In saveLocations I would not recommend string location = go.transform.position.ToString();. This will only give you one decimal e.g. "1.0f". I used ToString("F4"), what gives 4 decimals.

Further, for me float.Parse(sArray[0]) in StringToVector3 did not work as expected for me. In my case, the decimal point in the string is simply ignored. Therefore, the example from above would give me 10f after loading.

In my case I just divide that by 10000 now, like float.Parse(sArray[0]) / 10000 or rather float.Parse(sArray[0]) / 10^4 with 4 being from "F4".