Initial Commit

This commit is contained in:
2025-07-04 20:33:06 +03:00
commit 8f09347ae0
9219 changed files with 2447903 additions and 0 deletions
@@ -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);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c1ca019a7ed88b84792a6d0610555b21
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 36623bf873a22fc498789172d2842726
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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
{
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cc7f55566ebbfa845a8c4e2b83070915
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f5d0a6eb3cf408a4dbab4ae40f82517c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d24718d7dbd471d4b91ff903813c15b1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b12c97ae506923d4db53d6211b74bb7e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: