Initial Commit
This commit is contained in:
+83
@@ -0,0 +1,83 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// This sample shows you how to implement a scene manager with the following features:
|
||||
/// * Fetch all room scene anchors
|
||||
/// * Fetch all child scene anchors of a room
|
||||
/// * Set the location, and name of the object as label
|
||||
/// * Spawn primitive geometry to match the scene anchor's plane, volume or mesh data
|
||||
///
|
||||
/// There is a fallback for running scene capture if no rooms were found.
|
||||
/// </summary>
|
||||
public class BasicSceneManager : MonoBehaviour
|
||||
{
|
||||
void Start()
|
||||
{
|
||||
SceneManagerHelper.RequestScenePermission();
|
||||
|
||||
LoadSceneAsync();
|
||||
}
|
||||
|
||||
async void LoadSceneAsync()
|
||||
{
|
||||
// fetch all rooms, with a SceneCapture fallback
|
||||
var rooms = new List<OVRAnchor>();
|
||||
await OVRAnchor.FetchAnchorsAsync<OVRRoomLayout>(rooms);
|
||||
if (rooms.Count == 0)
|
||||
{
|
||||
var sceneCaptured = await SceneManagerHelper.RequestSceneCapture();
|
||||
if (!sceneCaptured)
|
||||
return;
|
||||
|
||||
await OVRAnchor.FetchAnchorsAsync<OVRRoomLayout>(rooms);
|
||||
}
|
||||
|
||||
// fetch room elements, create objects for them
|
||||
var tasks = rooms.Select(async room =>
|
||||
{
|
||||
var roomObject = new GameObject($"Room-{room.Uuid}");
|
||||
if (!room.TryGetComponent(out OVRAnchorContainer container))
|
||||
return;
|
||||
|
||||
var children = new List<OVRAnchor>();
|
||||
await container.FetchChildrenAsync(children);
|
||||
await CreateSceneAnchors(roomObject, children);
|
||||
}).ToList();
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
async Task CreateSceneAnchors(GameObject roomGameObject, List<OVRAnchor> anchors)
|
||||
{
|
||||
// we create tasks to iterate all anchors in parallel
|
||||
var tasks = anchors.Select(async anchor =>
|
||||
{
|
||||
// can we locate it in the world?
|
||||
if (!anchor.TryGetComponent(out OVRLocatable locatable))
|
||||
return;
|
||||
await locatable.SetEnabledAsync(true);
|
||||
|
||||
// get semantic classification for object name
|
||||
var label = "other";
|
||||
if (anchor.TryGetComponent(out OVRSemanticLabels labels))
|
||||
label = labels.Labels;
|
||||
|
||||
// create and parent Unity game object
|
||||
var gameObject = new GameObject(label);
|
||||
gameObject.transform.SetParent(roomGameObject.transform);
|
||||
|
||||
// set location and create objects for 2D, 3D, triangle mesh
|
||||
var helper = new SceneManagerHelper(gameObject);
|
||||
helper.SetLocation(locatable);
|
||||
|
||||
if (anchor.TryGetComponent(out OVRBounded2D b2d) && b2d.IsEnabled)
|
||||
helper.CreatePlane(b2d);
|
||||
if (anchor.TryGetComponent(out OVRBounded3D b3d) && b3d.IsEnabled)
|
||||
helper.CreateVolume(b3d);
|
||||
}).ToList();
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1ca019a7ed88b84792a6d0610555b21
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using DynamicSceneManagerHelper;
|
||||
|
||||
/// <summary>
|
||||
/// This sample expands on the snapshot scene manager and adds the ability
|
||||
/// to update Unity game objects as the data changes through the snapshots.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As per the anchor change rules, the UUID of a room changes when a child
|
||||
/// item is added or removed, so we match pairs of anchors that have similar
|
||||
/// child anchors between snapshots. We also check the geometric bounds of
|
||||
/// anchors across snapshots.
|
||||
///
|
||||
/// In order to achieve this, we save additional info in scene snapshots,
|
||||
/// and also provide a way to update existing Unity objects when
|
||||
/// their geometry changes.
|
||||
///
|
||||
/// This sample should serve as a guide only, as it relies on many heap
|
||||
/// allocations and inefficient search queries to help readability.
|
||||
/// </remarks>
|
||||
public class DynamicSceneManager : MonoBehaviour
|
||||
{
|
||||
public float UpdateFrequencySeconds = 5;
|
||||
|
||||
SceneSnapshot _snapshot = new SceneSnapshot();
|
||||
Dictionary<OVRAnchor, GameObject> _sceneGameObjects = new Dictionary<OVRAnchor, GameObject>();
|
||||
|
||||
Task _updateSceneTask;
|
||||
|
||||
void Start()
|
||||
{
|
||||
SceneManagerHelper.RequestScenePermission();
|
||||
StartCoroutine(UpdateScenePeriodically());
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (OVRInput.GetDown(OVRInput.RawButton.A))
|
||||
_ = SceneManagerHelper.RequestSceneCapture();
|
||||
}
|
||||
|
||||
IEnumerator UpdateScenePeriodically()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return new WaitForSeconds(UpdateFrequencySeconds);
|
||||
|
||||
_updateSceneTask = UpdateScene();
|
||||
yield return new WaitUntil(() => _updateSceneTask.IsCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
async Task UpdateScene()
|
||||
{
|
||||
// get current snapshot and compare to previous
|
||||
var currentSnapshot = await LoadSceneSnapshotAsync();
|
||||
var differences = new SnapshotComparer(
|
||||
_snapshot, currentSnapshot).Compare();
|
||||
|
||||
// update unity objects from the differences
|
||||
await UpdateUnityObjects(differences, currentSnapshot);
|
||||
|
||||
// update previous snapshot
|
||||
_snapshot = currentSnapshot;
|
||||
}
|
||||
|
||||
async Task<SceneSnapshot> LoadSceneSnapshotAsync()
|
||||
{
|
||||
// create snapshot from all rooms and their anchors
|
||||
// saving some of the anchor data to detect changes
|
||||
var snapshot = new SceneSnapshot();
|
||||
|
||||
var rooms = new List<OVRAnchor>();
|
||||
await OVRAnchor.FetchAnchorsAsync<OVRRoomLayout>(rooms);
|
||||
foreach (var room in rooms)
|
||||
{
|
||||
if (!room.TryGetComponent(out OVRAnchorContainer container))
|
||||
continue;
|
||||
|
||||
var children = new List<OVRAnchor>();
|
||||
await container.FetchChildrenAsync(children);
|
||||
snapshot.Anchors.Add(room, new SceneSnapshot.Data { Children = children});
|
||||
|
||||
foreach (var child in children)
|
||||
{
|
||||
var data = new SceneSnapshot.Data();
|
||||
if (child.TryGetComponent(out OVRBounded2D b2d) && b2d.IsEnabled)
|
||||
data.Rect = b2d.BoundingBox;
|
||||
if (child.TryGetComponent(out OVRBounded3D b3d) && b3d.IsEnabled)
|
||||
data.Bounds = b3d.BoundingBox;
|
||||
snapshot.Anchors.Add(child, data);
|
||||
}
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
async Task UpdateUnityObjects(List<(OVRAnchor, SnapshotComparer.ChangeType)> changes,
|
||||
SceneSnapshot newSnapshot)
|
||||
{
|
||||
if (!changes.Any())
|
||||
return;
|
||||
|
||||
var updater = new UnityObjectUpdater();
|
||||
|
||||
var changesNew = FilterChanges(changes, SnapshotComparer.ChangeType.New);
|
||||
var changesMissing = FilterChanges(changes, SnapshotComparer.ChangeType.Missing);
|
||||
var changesId = FilterChanges(changes, SnapshotComparer.ChangeType.ChangedId);
|
||||
var changesBounds = FilterChanges(changes, SnapshotComparer.ChangeType.ChangedBounds);
|
||||
|
||||
// create a new game object for all new changes
|
||||
foreach (var anchor in changesNew)
|
||||
{
|
||||
_sceneGameObjects.TryGetValue(GetParentAnchor(anchor, newSnapshot), out var parent);
|
||||
_sceneGameObjects.Add(anchor, await updater.CreateUnityObject(anchor, parent));
|
||||
}
|
||||
|
||||
// destroy game objects for all missing anchors
|
||||
foreach (var anchor in changesMissing)
|
||||
{
|
||||
Destroy(_sceneGameObjects[anchor]);
|
||||
_sceneGameObjects.Remove(anchor);
|
||||
}
|
||||
|
||||
// ChangedId means we need to find the pairs between the snapshots
|
||||
foreach (var (currentAnchor, newAnchor) in FindAnchorPairs(changesId, newSnapshot))
|
||||
{
|
||||
// we only need to update the reference in our scene game objects
|
||||
_sceneGameObjects.Add(newAnchor, _sceneGameObjects[currentAnchor]);
|
||||
_sceneGameObjects.Remove(currentAnchor);
|
||||
}
|
||||
|
||||
// geometry bounds means just updating an existing game object
|
||||
foreach (var currentAnchor in changesBounds)
|
||||
updater.UpdateUnityObject(currentAnchor, _sceneGameObjects[currentAnchor]);
|
||||
}
|
||||
|
||||
List<OVRAnchor> FilterChanges(List<(OVRAnchor, SnapshotComparer.ChangeType)> changes,
|
||||
SnapshotComparer.ChangeType changeType) =>
|
||||
changes.Where(tuple => tuple.Item2 == changeType).Select(tuple => tuple.Item1).ToList();
|
||||
|
||||
List<(OVRAnchor, OVRAnchor)> FindAnchorPairs(List<OVRAnchor> allAnchors, SceneSnapshot newSnapshot)
|
||||
{
|
||||
var currentAnchors = allAnchors.Where(_snapshot.Contains);
|
||||
var newAnchors = allAnchors.Where(newSnapshot.Contains);
|
||||
|
||||
var pairs = new List<(OVRAnchor, OVRAnchor)>();
|
||||
foreach (var currentAnchor in currentAnchors)
|
||||
{
|
||||
foreach (var newAnchor in newAnchors)
|
||||
{
|
||||
if (AreAnchorsEqual(_snapshot.Anchors[currentAnchor], newSnapshot.Anchors[newAnchor]))
|
||||
{
|
||||
pairs.Add((currentAnchor, newAnchor));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return pairs;
|
||||
}
|
||||
|
||||
bool AreAnchorsEqual(SceneSnapshot.Data anchor1Data, SceneSnapshot.Data anchor2Data)
|
||||
{
|
||||
// the only equal anchors with different UUIDs are when they are rooms.
|
||||
// so we will check if any of their child elements are the same
|
||||
if (anchor1Data.Children == null || anchor2Data.Children == null)
|
||||
return false;
|
||||
|
||||
return anchor1Data.Children.Any(anchor2Data.Children.Contains) ||
|
||||
anchor2Data.Children.Any(anchor1Data.Children.Contains);
|
||||
}
|
||||
|
||||
OVRAnchor GetParentAnchor(OVRAnchor childAnchor, SceneSnapshot snapshot)
|
||||
{
|
||||
foreach (var kvp in snapshot.Anchors)
|
||||
if (kvp.Value.Children?.Contains(childAnchor) == true)
|
||||
return kvp.Key;
|
||||
return OVRAnchor.Null;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36623bf873a22fc498789172d2842726
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
// This namespace contains helper classes for the DynamicSceneManager.
|
||||
namespace DynamicSceneManagerHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// A class that holds a list of anchors, along with data that
|
||||
/// can be used to identify when a change has occured.
|
||||
/// </summary>
|
||||
class SceneSnapshot
|
||||
{
|
||||
public class Data
|
||||
{
|
||||
public List<OVRAnchor> Children;
|
||||
public Rect? Rect;
|
||||
public Bounds? Bounds;
|
||||
}
|
||||
public Dictionary<OVRAnchor, Data> Anchors { get; } = new Dictionary<OVRAnchor, Data>();
|
||||
public bool Contains(OVRAnchor anchor) => Anchors.ContainsKey(anchor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class contains the custom logic for scene snapshot comparison.
|
||||
/// </summary>
|
||||
class SnapshotComparer
|
||||
{
|
||||
public SceneSnapshot BaseSnapshot { get; }
|
||||
public SceneSnapshot NewSnapshot { get; }
|
||||
|
||||
public SnapshotComparer(SceneSnapshot baseSnapshot, SceneSnapshot newSnapshot)
|
||||
{
|
||||
BaseSnapshot = baseSnapshot;
|
||||
NewSnapshot = newSnapshot;
|
||||
}
|
||||
|
||||
public enum ChangeType
|
||||
{
|
||||
New,
|
||||
Missing,
|
||||
ChangedId,
|
||||
ChangedBounds
|
||||
}
|
||||
|
||||
public List<(OVRAnchor, ChangeType)> Compare()
|
||||
{
|
||||
var changes = new List<(OVRAnchor, ChangeType)>();
|
||||
|
||||
// if in base but not in new, then missing
|
||||
// if in new but not in base, then new
|
||||
foreach (var anchor1 in BaseSnapshot.Anchors.Keys)
|
||||
if (!NewSnapshot.Contains(anchor1))
|
||||
changes.Add((anchor1, ChangeType.Missing));
|
||||
foreach (var anchor2 in NewSnapshot.Anchors.Keys)
|
||||
if (!BaseSnapshot.Contains(anchor2))
|
||||
changes.Add((anchor2, ChangeType.New));
|
||||
|
||||
// further checks
|
||||
CheckRoomChanges(changes);
|
||||
CheckBoundsChanges(changes);
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
void CheckRoomChanges(List<(OVRAnchor, ChangeType)> changes)
|
||||
{
|
||||
// a room with new/deleted/edited child anchors is considered
|
||||
// a NEW anchor, so we will check if any child elements are
|
||||
// the same and mark both old and new room anchors as CHANGED
|
||||
for (var i = 0; i < changes.Count; i++)
|
||||
{
|
||||
var (anchor, change) = changes[i];
|
||||
|
||||
var isRoom = anchor.TryGetComponent(out OVRRoomLayout room) && room.IsEnabled;
|
||||
if (!isRoom || change == ChangeType.ChangedId)
|
||||
continue;
|
||||
|
||||
var isNewAnchor = NewSnapshot.Contains(anchor);
|
||||
var isOldAnchor = BaseSnapshot.Contains(anchor);
|
||||
if (!isNewAnchor && !isOldAnchor)
|
||||
continue;
|
||||
|
||||
var children = isNewAnchor ?
|
||||
NewSnapshot.Anchors[anchor].Children :
|
||||
BaseSnapshot.Anchors[anchor].Children;
|
||||
var comparisonSnapshot = change == ChangeType.New ?
|
||||
BaseSnapshot : NewSnapshot;
|
||||
|
||||
foreach (var child in children)
|
||||
if (comparisonSnapshot.Contains(child))
|
||||
changes[i] = (anchor, ChangeType.ChangedId);
|
||||
}
|
||||
}
|
||||
|
||||
void CheckBoundsChanges(List<(OVRAnchor, ChangeType)> changes)
|
||||
{
|
||||
// first match pairs of base and new snapshots
|
||||
foreach (var baseAnchor in BaseSnapshot.Anchors.Keys)
|
||||
{
|
||||
var newAnchor = NewSnapshot.Anchors.Keys.FirstOrDefault(
|
||||
newAnchor => newAnchor.Uuid == baseAnchor.Uuid);
|
||||
|
||||
// we have a pair, now compare bounds data
|
||||
if (newAnchor.Uuid == baseAnchor.Uuid)
|
||||
{
|
||||
var baseData = BaseSnapshot.Anchors[baseAnchor];
|
||||
var newData = NewSnapshot.Anchors[newAnchor];
|
||||
|
||||
var changed2DBounds = Has2DBounds(baseData, newData) && Are2DBoundsDifferent(baseData, newData);
|
||||
var changed3DBounds = Has3DBounds(baseData, newData) && Are3DBoundsDifferent(baseData, newData);
|
||||
|
||||
if (changed2DBounds || changed3DBounds)
|
||||
changes.Add((baseAnchor, ChangeType.ChangedBounds));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Has2DBounds(SceneSnapshot.Data data1, SceneSnapshot.Data data2)
|
||||
=> data1.Rect.HasValue && data2.Rect.HasValue;
|
||||
bool Are2DBoundsDifferent(SceneSnapshot.Data data1, SceneSnapshot.Data data2)
|
||||
=> data1.Rect?.min != data2.Rect?.min || data1.Rect?.max != data2.Rect?.max;
|
||||
bool Has3DBounds(SceneSnapshot.Data data1, SceneSnapshot.Data data2)
|
||||
=> data1.Bounds.HasValue && data2.Bounds.HasValue;
|
||||
bool Are3DBoundsDifferent(SceneSnapshot.Data data1, SceneSnapshot.Data data2)
|
||||
=> data1.Bounds?.min != data2.Bounds?.min || data1.Bounds?.max != data2.Bounds?.max;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class wraps the logic for interacting with Unity
|
||||
/// game objects.
|
||||
/// </summary>
|
||||
class UnityObjectUpdater
|
||||
{
|
||||
public async Task<GameObject> CreateUnityObject(OVRAnchor anchor, GameObject parent)
|
||||
{
|
||||
// if this is a room, we only need to make a GameObject
|
||||
if (anchor.TryGetComponent(out OVRRoomLayout _))
|
||||
return new GameObject($"Room-{anchor.Uuid}");
|
||||
|
||||
// only interested in the anchors which are locatable
|
||||
if (!anchor.TryGetComponent(out OVRLocatable locatable))
|
||||
return null;
|
||||
await locatable.SetEnabledAsync(true);
|
||||
|
||||
// get semantic classification for object name
|
||||
var label = "other";
|
||||
if (anchor.TryGetComponent(out OVRSemanticLabels labels))
|
||||
label = labels.Labels;
|
||||
|
||||
// create and parent Unity game object if possible
|
||||
var gameObject = new GameObject(label);
|
||||
if (parent != null)
|
||||
gameObject.transform.SetParent(parent.transform);
|
||||
|
||||
// set location and create objects for 2D, 3D, triangle mesh
|
||||
var helper = new SceneManagerHelper(gameObject);
|
||||
helper.SetLocation(locatable);
|
||||
|
||||
if (anchor.TryGetComponent(out OVRBounded2D b2d) && b2d.IsEnabled)
|
||||
helper.CreatePlane(b2d);
|
||||
if (anchor.TryGetComponent(out OVRBounded3D b3d) && b3d.IsEnabled)
|
||||
helper.CreateVolume(b3d);
|
||||
if (anchor.TryGetComponent(out OVRTriangleMesh mesh) && mesh.IsEnabled)
|
||||
helper.CreateMesh(mesh);
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
public void UpdateUnityObject(OVRAnchor anchor, GameObject gameObject)
|
||||
{
|
||||
var helper = new SceneManagerHelper(gameObject);
|
||||
if (anchor.TryGetComponent(out OVRLocatable locatable))
|
||||
helper.SetLocation(locatable);
|
||||
if (anchor.TryGetComponent(out OVRBounded2D b2d) && b2d.IsEnabled)
|
||||
helper.UpdatePlane(b2d);
|
||||
if (anchor.TryGetComponent(out OVRBounded3D b3d) && b3d.IsEnabled)
|
||||
helper.UpdateVolume(b3d);
|
||||
if (anchor.TryGetComponent(out OVRTriangleMesh mesh) && mesh.IsEnabled)
|
||||
helper.UpdateMesh(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
static class ObjectPool
|
||||
{
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc7f55566ebbfa845a8c4e2b83070915
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// This sample expands on the basic scene manager and adds the following features:
|
||||
/// * Spawn a prefab for the wall, ceiling, floor elements and set 2D dimensions
|
||||
/// * Spawn a fallback object for all other semantic labels and set 3D dimensions
|
||||
/// * Update the location of all anchors at some frequency with new tracking info
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The prefabs in this class are scaled to dimensions 1, however, you will need to
|
||||
/// consider the dimension within your prefab to scale correctly. Additionally, in
|
||||
/// order to avoid stretching, you may consider 9-slicing.
|
||||
///
|
||||
/// Scene anchors are tracked independently and can change as new tracking information
|
||||
/// becomes available. It may be better for your use case to consider a single
|
||||
/// tracking point and only update that. This will keep the relative positions of
|
||||
/// all objects consistent, while also updating the location with new tracking data.
|
||||
/// </remarks>
|
||||
public class PrefabSceneManager : MonoBehaviour
|
||||
{
|
||||
public GameObject WallPrefab;
|
||||
public GameObject CeilingPrefab;
|
||||
public GameObject FloorPrefab;
|
||||
public GameObject FallbackPrefab;
|
||||
public float UpdateFrequencySeconds = 5;
|
||||
|
||||
List<(GameObject,OVRLocatable)> _locatableObjects = new List<(GameObject,OVRLocatable)>();
|
||||
|
||||
void Start()
|
||||
{
|
||||
SceneManagerHelper.RequestScenePermission();
|
||||
|
||||
LoadSceneAsync();
|
||||
StartCoroutine(UpdateAnchorsPeriodically());
|
||||
}
|
||||
|
||||
async void LoadSceneAsync()
|
||||
{
|
||||
// fetch all rooms, with a SceneCapture fallback
|
||||
var rooms = new List<OVRAnchor>();
|
||||
await OVRAnchor.FetchAnchorsAsync<OVRRoomLayout>(rooms);
|
||||
if (rooms.Count == 0)
|
||||
{
|
||||
var sceneCaptured = await SceneManagerHelper.RequestSceneCapture();
|
||||
if (!sceneCaptured)
|
||||
return;
|
||||
|
||||
await OVRAnchor.FetchAnchorsAsync<OVRRoomLayout>(rooms);
|
||||
}
|
||||
|
||||
// fetch room elements, create objects for them
|
||||
var tasks = rooms.Select(async room =>
|
||||
{
|
||||
var roomObject = new GameObject($"Room-{room.Uuid}");
|
||||
if (!room.TryGetComponent(out OVRAnchorContainer container))
|
||||
return;
|
||||
if (!room.TryGetComponent(out OVRRoomLayout roomLayout))
|
||||
return;
|
||||
|
||||
var children = new List<OVRAnchor>();
|
||||
await container.FetchChildrenAsync(children);
|
||||
await CreateSceneAnchors(roomObject, roomLayout, children);
|
||||
}).ToList();
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
async Task CreateSceneAnchors(GameObject roomGameObject,
|
||||
OVRRoomLayout roomLayout, List<OVRAnchor> anchors)
|
||||
{
|
||||
roomLayout.TryGetRoomLayout(out var ceilingUuid,
|
||||
out var floorUuid, out var wallUuids);
|
||||
|
||||
// iterate over all anchors as async tasks
|
||||
var tasks = anchors.Select(async anchor =>
|
||||
{
|
||||
// can we locate it in the world?
|
||||
if (!anchor.TryGetComponent(out OVRLocatable locatable))
|
||||
return;
|
||||
await locatable.SetEnabledAsync(true);
|
||||
|
||||
// check room layout information and assign prefab
|
||||
// it would also be possible to use the semantic label
|
||||
var prefab = FallbackPrefab;
|
||||
if (anchor.Uuid == floorUuid)
|
||||
prefab = FloorPrefab;
|
||||
else if (anchor.Uuid == ceilingUuid)
|
||||
prefab = CeilingPrefab;
|
||||
else if (wallUuids.Contains(anchor.Uuid))
|
||||
prefab = WallPrefab;
|
||||
|
||||
// get semantic classification for object name
|
||||
var label = "other";
|
||||
if (anchor.TryGetComponent(out OVRSemanticLabels labels))
|
||||
label = labels.Labels;
|
||||
|
||||
// create container object
|
||||
var gameObject = new GameObject(label);
|
||||
gameObject.transform.SetParent(roomGameObject.transform);
|
||||
var helper = new SceneManagerHelper(gameObject);
|
||||
helper.SetLocation(locatable);
|
||||
|
||||
// instantiate prefab & set 2D dimensions
|
||||
var model = Instantiate(prefab, gameObject.transform);
|
||||
if (anchor.TryGetComponent(out OVRBounded2D bounds2D) &&
|
||||
bounds2D.IsEnabled)
|
||||
{
|
||||
model.transform.localScale = new Vector3(
|
||||
bounds2D.BoundingBox.size.x,
|
||||
bounds2D.BoundingBox.size.y,
|
||||
0.01f);
|
||||
}
|
||||
|
||||
// we will set volume dimensions for the non-room elements
|
||||
if (prefab == FallbackPrefab)
|
||||
{
|
||||
if (anchor.TryGetComponent(out OVRBounded3D bounds3D) &&
|
||||
bounds3D.IsEnabled)
|
||||
{
|
||||
model.transform.localPosition = new Vector3(0, 0,
|
||||
-bounds3D.BoundingBox.size.z / 2);
|
||||
model.transform.localScale = bounds3D.BoundingBox.size;
|
||||
}
|
||||
}
|
||||
|
||||
// save game object and locatable for updating later
|
||||
_locatableObjects.Add((gameObject, locatable));
|
||||
}).ToList();
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
IEnumerator UpdateAnchorsPeriodically()
|
||||
{
|
||||
while (true) {
|
||||
foreach (var (gameObject, locatable) in _locatableObjects)
|
||||
{
|
||||
var helper = new SceneManagerHelper(gameObject);
|
||||
helper.SetLocation(locatable);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(UpdateFrequencySeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5d0a6eb3cf408a4dbab4ae40f82517c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// A smaller helper class for Custom Scene Manager samples.
|
||||
/// </summary>
|
||||
public class SceneManagerHelper
|
||||
{
|
||||
public GameObject AnchorGameObject { get; }
|
||||
|
||||
public SceneManagerHelper(GameObject gameObject)
|
||||
{
|
||||
AnchorGameObject = gameObject;
|
||||
}
|
||||
|
||||
public void SetLocation(OVRLocatable locatable, Camera camera = null)
|
||||
{
|
||||
if (!locatable.TryGetSceneAnchorPose(out var pose))
|
||||
return;
|
||||
|
||||
var projectionCamera = camera == null ? Camera.main : camera;
|
||||
var position = pose.ComputeWorldPosition(projectionCamera);
|
||||
var rotation = pose.ComputeWorldRotation(projectionCamera);
|
||||
|
||||
if (position != null && rotation != null)
|
||||
AnchorGameObject.transform.SetPositionAndRotation(
|
||||
position.Value, rotation.Value);
|
||||
}
|
||||
|
||||
public void CreatePlane(OVRBounded2D bounds)
|
||||
{
|
||||
var planeGO = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
planeGO.name = "Plane";
|
||||
planeGO.transform.SetParent(AnchorGameObject.transform, false);
|
||||
planeGO.transform.localScale = new Vector3(
|
||||
bounds.BoundingBox.size.x,
|
||||
bounds.BoundingBox.size.y,
|
||||
0.01f);
|
||||
planeGO.GetComponent<MeshRenderer>().material.SetColor(
|
||||
"_Color", UnityEngine.Random.ColorHSV());
|
||||
}
|
||||
|
||||
public void UpdatePlane(OVRBounded2D bounds)
|
||||
{
|
||||
var planeGO = AnchorGameObject.transform.Find("Plane");
|
||||
if (planeGO == null)
|
||||
CreatePlane(bounds);
|
||||
else
|
||||
{
|
||||
planeGO.transform.localScale = new Vector3(
|
||||
bounds.BoundingBox.size.x,
|
||||
bounds.BoundingBox.size.y,
|
||||
0.01f);
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateVolume(OVRBounded3D bounds)
|
||||
{
|
||||
var volumeGO = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
volumeGO.name = "Volume";
|
||||
volumeGO.transform.SetParent(AnchorGameObject.transform, false);
|
||||
volumeGO.transform.localPosition = new Vector3(
|
||||
0, 0, -bounds.BoundingBox.size.z / 2);
|
||||
volumeGO.transform.localScale = bounds.BoundingBox.size;
|
||||
volumeGO.GetComponent<MeshRenderer>().material.SetColor(
|
||||
"_Color", UnityEngine.Random.ColorHSV());
|
||||
}
|
||||
|
||||
public void UpdateVolume(OVRBounded3D bounds)
|
||||
{
|
||||
var volumeGO = AnchorGameObject.transform.Find("Volume");
|
||||
if (volumeGO == null)
|
||||
CreateVolume(bounds);
|
||||
else
|
||||
{
|
||||
volumeGO.transform.localPosition = new Vector3(
|
||||
0, 0, -bounds.BoundingBox.size.z / 2);
|
||||
volumeGO.transform.localScale = bounds.BoundingBox.size;
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateMesh(OVRTriangleMesh mesh)
|
||||
{
|
||||
if (!mesh.TryGetCounts(out var vcount, out var tcount)) return;
|
||||
using var vs = new NativeArray<Vector3>(vcount, Allocator.Temp);
|
||||
using var ts = new NativeArray<int>(tcount * 3, Allocator.Temp);
|
||||
if (!mesh.TryGetMesh(vs, ts)) return;
|
||||
|
||||
var trimesh = new Mesh();
|
||||
trimesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
|
||||
trimesh.SetVertices(vs);
|
||||
trimesh.SetTriangles(ts.ToArray(), 0);
|
||||
|
||||
var meshGO = GameObject.CreatePrimitive(PrimitiveType.Quad);
|
||||
meshGO.name = "Mesh";
|
||||
meshGO.transform.SetParent(AnchorGameObject.transform, false);
|
||||
meshGO.GetComponent<MeshFilter>().sharedMesh = trimesh;
|
||||
meshGO.GetComponent<MeshCollider>().sharedMesh = trimesh;
|
||||
meshGO.GetComponent<MeshRenderer>().material.SetColor(
|
||||
"_Color", UnityEngine.Random.ColorHSV());
|
||||
}
|
||||
|
||||
public void UpdateMesh(OVRTriangleMesh mesh)
|
||||
{
|
||||
var meshGO = AnchorGameObject.transform.Find("Mesh");
|
||||
if (meshGO != null) UnityEngine.Object.Destroy(meshGO);
|
||||
CreateMesh(mesh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A wrapper function for requesting Scene Capture.
|
||||
/// </summary>
|
||||
public static async Task<bool> RequestSceneCapture()
|
||||
{
|
||||
if (SceneCaptureRunning) return false;
|
||||
SceneCaptureRunning = true;
|
||||
|
||||
var waiting = true;
|
||||
Action<ulong, bool> onCaptured = (id, success) => { waiting = false; };
|
||||
|
||||
// subscribe, make non-blocking call, yield and wait
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
OVRManager.SceneCaptureComplete += onCaptured;
|
||||
if (!OVRPlugin.RequestSceneCapture("", out var _))
|
||||
{
|
||||
OVRManager.SceneCaptureComplete -= onCaptured;
|
||||
SceneCaptureRunning = false;
|
||||
return false;
|
||||
}
|
||||
while (waiting) Task.Delay(200);
|
||||
OVRManager.SceneCaptureComplete -= onCaptured;
|
||||
SceneCaptureRunning = false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
private static bool SceneCaptureRunning = false; // single instance
|
||||
|
||||
/// <summary>
|
||||
/// A wrapper function for requesting the Android
|
||||
/// permission for scene data.
|
||||
/// </summary>
|
||||
public static void RequestScenePermission()
|
||||
{
|
||||
const string permission = "com.oculus.permission.USE_SCENE";
|
||||
if (!UnityEngine.Android.Permission.HasUserAuthorizedPermission(permission))
|
||||
UnityEngine.Android.Permission.RequestUserPermission(permission);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d24718d7dbd471d4b91ff903813c15b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// This sample shows you how to listen to changes in the underlying scene.
|
||||
///
|
||||
/// At a given update frequency, we query all anchors in the scene and create
|
||||
/// a snapshot. This is compared to a previous snapshot, and the
|
||||
/// differences are logged to the console.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In order to create new scene items, you need to run Scene Capture, so
|
||||
/// this sample will only work on-device (as PC Link does not support it).
|
||||
///
|
||||
/// The logic involves checking whether anchors exist in the base snapshot
|
||||
/// and new snapshot. Furthermore, this sample contains the logic for
|
||||
/// rooms to be compared, as the room anchor is NEW whenever a child element
|
||||
/// is added or removed.
|
||||
/// </remarks>
|
||||
public class SnapshotSceneManager : MonoBehaviour
|
||||
{
|
||||
public float UpdateFrequencySeconds = 5;
|
||||
|
||||
SceneSnapshot _snapshot = new SceneSnapshot();
|
||||
|
||||
void Start()
|
||||
{
|
||||
SceneManagerHelper.RequestScenePermission();
|
||||
StartCoroutine(UpdateScenePeriodically());
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (OVRInput.GetDown(OVRInput.RawButton.A))
|
||||
_ = SceneManagerHelper.RequestSceneCapture();
|
||||
}
|
||||
|
||||
IEnumerator UpdateScenePeriodically()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return new WaitForSeconds(UpdateFrequencySeconds);
|
||||
UpdateScene();
|
||||
}
|
||||
}
|
||||
|
||||
async void UpdateScene()
|
||||
{
|
||||
// get current snapshot and compare to previous
|
||||
var currentSnapshot = await LoadSceneSnapshotAsync();
|
||||
var differences = await new SnapshotComparer(
|
||||
_snapshot, currentSnapshot).Compare();
|
||||
|
||||
// inform user of changes
|
||||
var sb = new StringBuilder();
|
||||
if (differences.Count > 0)
|
||||
{
|
||||
sb.AppendLine("---- SCENE SNAPSHOT ----");
|
||||
foreach (var (anchor, change) in differences)
|
||||
sb.AppendLine($"{change}: {AnchorInfo(anchor)}");
|
||||
Debug.Log(sb.ToString());
|
||||
}
|
||||
|
||||
// update previous snapshot
|
||||
_snapshot = currentSnapshot;
|
||||
}
|
||||
|
||||
async Task<SceneSnapshot> LoadSceneSnapshotAsync()
|
||||
{
|
||||
// create snapshot from all rooms and their anchors
|
||||
var snapshot = new SceneSnapshot();
|
||||
|
||||
var rooms = new List<OVRAnchor>();
|
||||
await OVRAnchor.FetchAnchorsAsync<OVRRoomLayout>(rooms);
|
||||
foreach (var room in rooms)
|
||||
{
|
||||
if (!room.TryGetComponent(out OVRAnchorContainer container))
|
||||
continue;
|
||||
|
||||
var children = new List<OVRAnchor>();
|
||||
await container.FetchChildrenAsync(children);
|
||||
snapshot.Anchors.Add(room);
|
||||
snapshot.Anchors.AddRange(children);
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
string AnchorInfo(OVRAnchor anchor)
|
||||
{
|
||||
if (anchor.TryGetComponent(out OVRRoomLayout room) && room.IsEnabled)
|
||||
return $"{anchor.Uuid} - ROOM";
|
||||
if (anchor.TryGetComponent(out OVRSemanticLabels labels) && labels.IsEnabled)
|
||||
return $"{anchor.Uuid} - {labels.Labels}";
|
||||
return $"{anchor.Uuid}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A basic container class that holds a list of anchors.
|
||||
/// This class could be extended to optimize the comparison
|
||||
/// between snapshots (such as keeping the room-child
|
||||
/// relationship, or storing the location and/or bounds)
|
||||
/// </summary>
|
||||
class SceneSnapshot
|
||||
{
|
||||
public List<OVRAnchor> Anchors { get; } = new List<OVRAnchor>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class contains the custom logic for scene snapshot comparison.
|
||||
/// </summary>
|
||||
class SnapshotComparer
|
||||
{
|
||||
public SceneSnapshot BaseSnapshot { get; }
|
||||
public SceneSnapshot NewSnapshot { get; }
|
||||
|
||||
public SnapshotComparer(SceneSnapshot baseSnapshot, SceneSnapshot newSnapshot)
|
||||
{
|
||||
BaseSnapshot = baseSnapshot;
|
||||
NewSnapshot = newSnapshot;
|
||||
}
|
||||
|
||||
public enum ChangeType
|
||||
{
|
||||
New,
|
||||
Missing,
|
||||
Changed
|
||||
}
|
||||
|
||||
public async Task<List<(OVRAnchor, ChangeType)>> Compare()
|
||||
{
|
||||
var changes = new List<(OVRAnchor, ChangeType)>();
|
||||
|
||||
// if in base but not in new, then missing
|
||||
// if in new but not in base, then new
|
||||
foreach (var anchor1 in BaseSnapshot.Anchors)
|
||||
if (!NewSnapshot.Anchors.Contains(anchor1))
|
||||
changes.Add((anchor1, ChangeType.Missing));
|
||||
foreach (var anchor2 in NewSnapshot.Anchors)
|
||||
if (!BaseSnapshot.Anchors.Contains(anchor2))
|
||||
changes.Add((anchor2, ChangeType.New));
|
||||
|
||||
// further custom checks
|
||||
await CheckRoomChanges(changes);
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
async Task CheckRoomChanges(List<(OVRAnchor, ChangeType)> changes)
|
||||
{
|
||||
// a room with new/deleted/edited child anchors is considered
|
||||
// a NEW anchor, so we will check if any child elements are
|
||||
// the same and mark both old and new room anchors as CHANGED
|
||||
for (var i = 0; i < changes.Count; i++)
|
||||
{
|
||||
var (anchor, change) = changes[i];
|
||||
|
||||
var isRoom = anchor.TryGetComponent(out OVRRoomLayout room) &&
|
||||
room.IsEnabled;
|
||||
if (!isRoom || change == ChangeType.Changed)
|
||||
continue;
|
||||
|
||||
var childAnchors = new List<OVRAnchor>();
|
||||
if (!await room.FetchLayoutAnchorsAsync(childAnchors))
|
||||
continue;
|
||||
|
||||
var comparisonAnchors = change == ChangeType.New ?
|
||||
BaseSnapshot.Anchors : NewSnapshot.Anchors;
|
||||
foreach (var childAnchor in childAnchors)
|
||||
if (comparisonAnchors.Contains(childAnchor))
|
||||
changes[i] = (anchor, ChangeType.Changed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b12c97ae506923d4db53d6211b74bb7e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user