Skip to content

Commit 40fd264

Browse files
committed
Create WorldAnchorManager to abstract saving and loading of persisted anchors
Updated TapToPlace to use this script. Updated TapToPlace test scene to include the WorldAnchorManager on the Managers GameObject.
1 parent 140791b commit 40fd264

File tree

4 files changed

+251
-95
lines changed

4 files changed

+251
-95
lines changed

Assets/HoloToolkit/SpatialMapping/Scripts/TapToPlace.cs

Lines changed: 10 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// Licensed under the MIT License. See LICENSE in the project root for license information.
33

44
using UnityEngine;
5-
using UnityEngine.VR.WSA;
6-
using UnityEngine.VR.WSA.Persistence;
75

86
namespace HoloToolkit.Unity
97
{
@@ -24,44 +22,16 @@ public partial class TapToPlace : MonoBehaviour
2422
public string SavedAnchorFriendlyName = "SavedAnchorFriendlyName";
2523

2624
/// <summary>
27-
/// Keeps track of anchors stored on local device.
25+
/// Manages persisted anchors.
2826
/// </summary>
29-
WorldAnchorStore anchorStore = null;
30-
31-
/// <summary>
32-
/// Locally saved wold anchor.
33-
/// </summary>
34-
WorldAnchor savedAnchor;
27+
WorldAnchorManager anchorManager;
3528

3629
bool placing = false;
3730

3831
void Start()
3932
{
40-
WorldAnchorStore.GetAsync(AnchorStoreReady);
41-
}
42-
43-
/// <summary>
44-
/// Called when the local anchor store is ready.
45-
/// </summary>
46-
/// <param name="store"></param>
47-
void AnchorStoreReady(WorldAnchorStore store)
48-
{
49-
anchorStore = store;
50-
51-
// Try to load a previously saved world anchor.
52-
savedAnchor = anchorStore.Load(SavedAnchorFriendlyName, gameObject);
53-
if (savedAnchor == null)
54-
{
55-
// Either world anchor was not saved / does not exist or has a different name.
56-
Debug.Log(gameObject.name + " : "+ "World anchor could not be loaded for this game object. Creating a new anchor.");
57-
58-
// Create anchor since one does not exist.
59-
CreateAnchor();
60-
}
61-
else
62-
{
63-
Debug.Log(gameObject.name + " : " + "World anchor loaded from anchor store and updated for this game object.");
64-
}
33+
anchorManager = WorldAnchorManager.Instance;
34+
anchorManager.AttachAnchor(this.gameObject, SavedAnchorFriendlyName);
6535
}
6636

6737
// Called by GazeGestureManager when the user performs a tap gesture.
@@ -77,24 +47,16 @@ void OnSelect()
7747
{
7848
SpatialMappingManager.Instance.DrawVisualMeshes = true;
7949

80-
Debug.Log(gameObject.name + " : " + "Removing existing world anchor if any.");
81-
82-
// Remove existing world anchor when moving an object.
83-
DestroyImmediate(gameObject.GetComponent<WorldAnchor>());
50+
Debug.Log(gameObject.name + " : Removing existing world anchor if any.");
8451

85-
// Delete existing world anchor from anchor store when moving an object.
86-
if (anchorStore != null)
87-
{
88-
anchorStore.Delete(SavedAnchorFriendlyName);
89-
}
52+
anchorManager.RemoveAnchor(gameObject);
9053
}
9154
// If the user is not in placing mode, hide the spatial mapping mesh.
9255
else
9356
{
9457
SpatialMappingManager.Instance.DrawVisualMeshes = false;
95-
9658
// Add world anchor when object placement is done.
97-
CreateAnchor();
59+
anchorManager.AttachAnchor(gameObject, SavedAnchorFriendlyName);
9860
}
9961
}
10062
else
@@ -103,57 +65,11 @@ void OnSelect()
10365
}
10466
}
10567

106-
private void CreateAnchor()
107-
{
108-
// NOTE: It's good practice to ensure your parent hierarchy or root game object does not already have a World Anchor.
109-
// You can handle this in a way that works best for your application.
110-
// For example: gameObject.transform.root.GetComponent<WorldAnchor>();
111-
112-
// Add the world anchor component when done moving an object.
113-
WorldAnchor anchor = gameObject.AddComponent<WorldAnchor>();
114-
if (anchor.isLocated)
115-
{
116-
SaveAnchor(anchor);
117-
}
118-
else
119-
{
120-
anchor.OnTrackingChanged += Anchor_OnTrackingChanged;
121-
}
122-
}
123-
124-
private void SaveAnchor(WorldAnchor anchor)
125-
{
126-
// Save the anchor to persist holograms across sessions.
127-
if (anchorStore.Save(SavedAnchorFriendlyName, anchor))
128-
{
129-
Debug.Log(gameObject.name + " : " + "World anchor saved successfully.");
130-
}
131-
else
132-
{
133-
Debug.LogError(gameObject.name + " : " + "World anchor save failed.");
134-
}
135-
}
136-
137-
private void Anchor_OnTrackingChanged(WorldAnchor self, bool located)
138-
{
139-
if (located)
140-
{
141-
Debug.Log(gameObject.name + " : " + "World anchor located successfully.");
142-
143-
SaveAnchor(self);
144-
self.OnTrackingChanged -= Anchor_OnTrackingChanged;
145-
}
146-
else
147-
{
148-
Debug.LogError(gameObject.name + " : " + "World anchor failed to locate.");
149-
}
150-
}
151-
15268
void Update()
15369
{
154-
// If the user is in placing mode,
155-
// update the placement to match the user's gaze.
156-
if (placing)
70+
// If the user is in placing mode,
71+
// update the placement to match the user's gaze.
72+
if (placing)
15773
{
15874
// Do a raycast into the world that will only hit the Spatial Mapping mesh.
15975
var headPosition = Camera.main.transform.position;

Assets/HoloToolkit/SpatialMapping/Tests/TapToPlace.unity

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ RenderSettings:
3737
m_ReflectionIntensity: 1
3838
m_CustomReflection: {fileID: 0}
3939
m_Sun: {fileID: 0}
40-
m_IndirectSpecularColor: {r: 0.44692582, g: 0.4967878, b: 0.5750859, a: 1}
40+
m_IndirectSpecularColor: {r: 0.44692558, g: 0.49678743, b: 0.57508624, a: 1}
4141
--- !u!157 &3
4242
LightmapSettings:
4343
m_ObjectHideFlags: 0
@@ -323,6 +323,7 @@ GameObject:
323323
- 4: {fileID: 2116021332}
324324
- 114: {fileID: 2116021331}
325325
- 114: {fileID: 2116021330}
326+
- 114: {fileID: 2116021333}
326327
m_Layer: 0
327328
m_Name: Managers
328329
m_TagString: Untagged
@@ -373,6 +374,17 @@ Transform:
373374
m_Children: []
374375
m_Father: {fileID: 0}
375376
m_RootOrder: 3
377+
--- !u!114 &2116021333
378+
MonoBehaviour:
379+
m_ObjectHideFlags: 0
380+
m_PrefabParentObject: {fileID: 0}
381+
m_PrefabInternal: {fileID: 0}
382+
m_GameObject: {fileID: 2116021329}
383+
m_Enabled: 1
384+
m_EditorHideFlags: 0
385+
m_Script: {fileID: 11500000, guid: f122ca4ae6b527e4798205becf9a0550, type: 3}
386+
m_Name:
387+
m_EditorClassIdentifier:
376388
--- !u!1 &2138978090
377389
GameObject:
378390
m_ObjectHideFlags: 0
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
using UnityEngine;
5+
using UnityEngine.VR.WSA.Persistence;
6+
using System.Collections.Generic;
7+
using UnityEngine.VR.WSA;
8+
9+
namespace HoloToolkit.Unity
10+
{
11+
/// <summary>
12+
/// Wrapper around world anchor store to streamline some of the
13+
/// persistence api busy work.
14+
/// </summary>
15+
public class WorldAnchorManager : Singleton<WorldAnchorManager>
16+
{
17+
/// <summary>
18+
/// To prevent initializing too many anchors at once
19+
/// and to allow for the WorldAnchorStore to load asyncronously
20+
/// without callers handling the case where the store isn't loaded yet
21+
/// we'll setup a queue of anchor attachment operations.
22+
/// The AnchorAttachmentInfo struct has the data needed to do this.
23+
/// </summary>
24+
private struct AnchorAttachmentInfo
25+
{
26+
public GameObject gameObjectToAnchor { get; set; }
27+
public string anchorName { get; set; }
28+
}
29+
30+
/// <summary>
31+
/// The queue mentioned above.
32+
/// </summary>
33+
private Queue<AnchorAttachmentInfo> anchorOperations = new Queue<AnchorAttachmentInfo>();
34+
35+
/// <summary>
36+
/// The WorldAnchorStore for the current application.
37+
/// Can be null when the application starts.
38+
/// </summary>
39+
public WorldAnchorStore anchorStore { get; set; }
40+
41+
/// <summary>
42+
/// Callback function that contains the WorldAnchorStore object.
43+
/// </summary>
44+
/// <param name="Store">The WorldAnchorStore to cache.</param>
45+
void AnchorStoreReady(WorldAnchorStore Store)
46+
{
47+
anchorStore = Store;
48+
}
49+
50+
/// <summary>
51+
/// When the app starts grab the anchor store immediately.
52+
/// </summary>
53+
void Awake()
54+
{
55+
anchorStore = null;
56+
WorldAnchorStore.GetAsync(AnchorStoreReady);
57+
}
58+
59+
/// <summary>
60+
/// Each frame see if there is work to do and if we can do a unit, do it.
61+
/// </summary>
62+
void Update()
63+
{
64+
if (anchorStore != null && anchorOperations.Count > 0)
65+
{
66+
DoAnchorOperation(anchorOperations.Dequeue());
67+
}
68+
}
69+
70+
/// <summary>
71+
/// Attaches an anchor to the game object. If the anchor store has
72+
/// an anchor with the specified name it will load the acnhor, otherwise
73+
/// a new anchor will be saved under the specified name.
74+
/// </summary>
75+
/// <param name="gameObjectToAnchor">The Gameobject to attach the anchor to.</param>
76+
/// <param name="AnchorName">Name of the anchor.</param>
77+
public void AttachAnchor(GameObject gameObjectToAnchor, string AnchorName)
78+
{
79+
if (gameObjectToAnchor == null)
80+
{
81+
Debug.LogError("Must pass in a valid gameObject");
82+
return;
83+
}
84+
85+
if (string.IsNullOrEmpty(AnchorName))
86+
{
87+
Debug.LogError("Must supply an AnchorName.");
88+
return;
89+
}
90+
91+
anchorOperations.Enqueue(
92+
new AnchorAttachmentInfo()
93+
{
94+
gameObjectToAnchor = gameObjectToAnchor,
95+
anchorName = AnchorName
96+
}
97+
);
98+
}
99+
100+
/// <summary>
101+
/// Removes the anchor from the game object and deletes the anchor
102+
/// from the anchor store.
103+
/// </summary>
104+
/// <param name="gameObjectToUnanchor">gameObject to remove the anchor from.</param>
105+
public void RemoveAnchor(GameObject gameObjectToUnanchor)
106+
{
107+
// This case is unexpected, but just in case.
108+
if (anchorStore == null)
109+
{
110+
Debug.LogError("remove anchor called before anchor store is ready.");
111+
}
112+
113+
WorldAnchor anchor = gameObjectToUnanchor.GetComponent<WorldAnchor>();
114+
115+
if (anchor != null)
116+
{
117+
anchorStore.Delete(anchor.name);
118+
DestroyImmediate(anchor);
119+
}
120+
}
121+
122+
/// <summary>
123+
/// Function that actually adds the anchor to the game object.
124+
/// </summary>
125+
/// <param name="anchorAttachmentInfo">Parameters for attaching the anchor.</param>
126+
void DoAnchorOperation(AnchorAttachmentInfo anchorAttachmentInfo)
127+
{
128+
string AnchorName = anchorAttachmentInfo.anchorName;
129+
GameObject gameObjectToAnchor = anchorAttachmentInfo.gameObjectToAnchor;
130+
131+
if (gameObjectToAnchor == null)
132+
{
133+
Debug.Log("GameObject must have been destroyed before we got a chance to anchor it.");
134+
return;
135+
}
136+
137+
// Try to load a previously saved world anchor.
138+
WorldAnchor savedAnchor = anchorStore.Load(AnchorName, gameObjectToAnchor);
139+
if (savedAnchor == null)
140+
{
141+
// Either world anchor was not saved / does not exist or has a different name.
142+
Debug.Log(gameObjectToAnchor.name + " : World anchor could not be loaded for this game object. Creating a new anchor.");
143+
144+
// Create anchor since one does not exist.
145+
CreateAnchor(gameObjectToAnchor, AnchorName);
146+
}
147+
else
148+
{
149+
savedAnchor.name = AnchorName;
150+
Debug.Log(gameObjectToAnchor.name + " : World anchor loaded from anchor store and updated for this game object.");
151+
}
152+
}
153+
154+
/// <summary>
155+
/// Creates an anchor, attaches it to the gameObjectToAnchor, and saves the anchor to the anchor store.
156+
/// </summary>
157+
/// <param name="gameObjectToAnchor">The GameObject to attach the anchor to.</param>
158+
/// <param name="anchorName">The name to give to the anchor.</param>
159+
void CreateAnchor(GameObject gameObjectToAnchor, string anchorName)
160+
{
161+
WorldAnchor anchor = gameObjectToAnchor.AddComponent<WorldAnchor>();
162+
anchor.name = anchorName;
163+
164+
// Sometimes the anchor is located immediately. In that case it can be saved immediately.
165+
if (anchor.isLocated)
166+
{
167+
SaveAnchor(anchor);
168+
}
169+
else
170+
{
171+
// Othertimes we must wait for the
172+
anchor.OnTrackingChanged += Anchor_OnTrackingChanged;
173+
}
174+
}
175+
176+
/// <summary>
177+
/// When an anchor isn't located immediately we subscribe to this event so
178+
/// we can save the anchor when it is finally located.
179+
/// </summary>
180+
/// <param name="self">The anchor that is reporting a tracking changed event.</param>
181+
/// <param name="located">Indicates if the anchor is located or not located.</param>
182+
private void Anchor_OnTrackingChanged(WorldAnchor self, bool located)
183+
{
184+
if (located)
185+
{
186+
Debug.Log(gameObject.name + " : World anchor located successfully.");
187+
188+
SaveAnchor(self);
189+
190+
// Once the anchor is located we can unsubscribe from this event.
191+
self.OnTrackingChanged -= Anchor_OnTrackingChanged;
192+
}
193+
else
194+
{
195+
Debug.LogError(gameObject.name + " : World anchor failed to locate.");
196+
}
197+
}
198+
199+
/// <summary>
200+
/// Saves the anchor to the anchor store.
201+
/// </summary>
202+
/// <param name="anchor"></param>
203+
private void SaveAnchor(WorldAnchor anchor)
204+
{
205+
// Save the anchor to persist holograms across sessions.
206+
if (anchorStore.Save(anchor.name, anchor))
207+
{
208+
Debug.Log(gameObject.name + " : World anchor saved successfully.");
209+
}
210+
else
211+
{
212+
Debug.LogError(gameObject.name + " : World anchor save failed.");
213+
}
214+
}
215+
}
216+
}

0 commit comments

Comments
 (0)