using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using DynamicSceneManagerHelper;
///
/// This sample expands on the snapshot scene manager and adds the ability
/// to update Unity game objects as the data changes through the snapshots.
///
///
/// 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.
///
public class DynamicSceneManager : MonoBehaviour
{
public float UpdateFrequencySeconds = 5;
SceneSnapshot _snapshot = new SceneSnapshot();
Dictionary _sceneGameObjects = new Dictionary();
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 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();
await OVRAnchor.FetchAnchorsAsync(rooms);
foreach (var room in rooms)
{
if (!room.TryGetComponent(out OVRAnchorContainer container))
continue;
var children = new List();
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 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 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;
}
}