VR4RoboticArm2/VR4RoboticArm/Library/PackageCache/com.meta.xr.sdk.core/Scripts/OVRSceneManager.cs
IonutMocanu d7aba243a2 Main
2025-09-08 11:04:02 +03:00

1288 lines
49 KiB
C#

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Meta.XR.Util;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Serialization;
using Debug = UnityEngine.Debug;
using Permission = UnityEngine.Android.Permission;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
using System.Linq;
#endif
/// <summary>
/// A manager for <see cref="OVRSceneAnchor"/>s created using the Room Setup feature.
/// </summary>
/// <remarks>
/// "OVRSceneManager and associated classes are deprecated (v65), please use [MR Utility Kit](https://developer.oculus.com/documentation/unity/unity-mr-utility-kit-overview)" instead.
/// </remarks>
[HelpURL("https://developer.oculus.com/documentation/unity/unity-scene-use-scene-anchors/")]
[Obsolete(DeprecationMessage)]
[Feature(Feature.Scene)]
public class OVRSceneManager : MonoBehaviour
{
internal const string DeprecationMessage =
"OVRSceneManager and associated classes are deprecated (v65), please use MR Utility Kit instead (https://developer.oculus.com/documentation/unity/unity-mr-utility-kit-overview)";
/// <summary>
/// A prefab that will be used to instantiate any Plane found
/// when querying the Scene model. If the anchor contains both
/// Volume and Plane elements, <see cref="VolumePrefab"/> will
/// be used instead. If null, no object will be instantiated,
/// unless a prefab override is provided.
/// </summary>
[FormerlySerializedAs("planePrefab")]
[Tooltip("A prefab that will be used to instantiate any Plane found " +
"when querying the Scene model. If the anchor contains both " +
"Volume and Plane elements, Volume will be used instead.")]
public OVRSceneAnchor PlanePrefab;
/// <summary>
/// A prefab that will be used to instantiate any Volume found
/// when querying the Scene model. This anchor may also contain
/// Plane elements. If null, no object will be instantiated,
/// unless a prefab override is provided.
/// </summary>
[FormerlySerializedAs("volumePrefab")]
[Tooltip("A prefab that will be used to instantiate any Volume found " +
"when querying the Scene model. This anchor may also contain " +
"Plane elements.")]
public OVRSceneAnchor VolumePrefab;
/// <summary>
/// Overrides the instantiation of the generic Plane and Volume prefabs with specialized ones.
/// If null is provided, no object will be instantiated for that label.
/// </summary>
[FormerlySerializedAs("prefabOverrides")]
[Tooltip("Overrides the instantiation of the generic Plane/Volume prefabs with specialized ones.")]
public List<OVRScenePrefabOverride> PrefabOverrides = new List<OVRScenePrefabOverride>();
/// <summary>
/// When enabled, only rooms the user is currently in will be instantiated.
/// </summary>
/// <remarks>
/// When `True`, <see cref="OVRSceneManager"/> will instantiate an <see cref="OVRSceneRoom"/> and all of its child
/// scene anchors (walls, floor, ceiling, and furniture) only if the user is located in the room when
/// <see cref="LoadSceneModel"/> is called.
///
/// When `False`, the <see cref="OVRSceneManager"/> will instantiate an <see cref="OVRSceneRoom"/> for each room,
/// regardless of the user's location.
///
/// The 2D boundary points of the room's floor are used to determine whether the user is inside a room.
///
/// If a room exists, but the user is not inside it, then the <see cref="NoSceneModelToLoad"/> event is invoked as
/// if the user had not yet run Space Setup.
/// </remarks>
[Tooltip("Scene manager will only present the room(s) the user is currently in.")]
public bool ActiveRoomsOnly = true;
/// <summary>
/// When true, verbose debug logs will be emitted.
/// </summary>
[FormerlySerializedAs("verboseLogging")]
[Tooltip("When enabled, verbose debug logs will be emitted.")]
public bool VerboseLogging;
/// <summary>
/// The maximum number of scene anchors that will be updated each frame.
/// </summary>
[Tooltip("The maximum number of scene anchors that will be updated each frame.")]
public int MaxSceneAnchorUpdatesPerFrame = 3;
/// <summary>
/// The parent transform to which each new <see cref="OVRSceneAnchor"/> or <see cref="OVRSceneRoom"/>
/// will be parented upon instantiation.
/// </summary>
/// <remarks>
/// if null, <see cref="OVRSceneRoom"/>(s) instantiated by <see cref="OVRSceneManager"/> will have no parent, and
/// <see cref="OVRSceneAnchor"/>(s) will have either a <see cref="OVRSceneRoom"/> as their parent or null, that is
/// they will be instantiated at the scene root. If non-null, <see cref="OVRSceneAnchor"/>(s) that do not
/// belong to any <see cref="OVRSceneRoom"/>, and <see cref="OVRSceneRoom"/>(s) along with its child
/// <see cref="OVRSceneAnchor"/>(s) will be parented to <see cref="InitialAnchorParent"/>.
///
/// Changing this value does not affect existing <see cref="OVRSceneAnchor"/>(s) or <see cref="OVRSceneRoom"/>(s).
/// </remarks>
public Transform InitialAnchorParent
{
get => _initialAnchorParent;
set => _initialAnchorParent = value;
}
[SerializeField]
[Tooltip("(Optional) The parent transform for each new scene anchor. " +
"Changing this value does not affect existing scene anchors. May be null.")]
internal Transform _initialAnchorParent;
#region Events
/// <summary>
/// This event fires when the <see cref="OVRSceneManager"/> has instantiated prefabs
/// for the Scene Anchors in a Scene Model.
/// </summary>
/// <remarks>
/// Wait until this event has been fired before accessing any <see cref="OVRSceneAnchor"/>s,
/// as this event waits for any additional initialization logic to be executed first.
/// Access <see cref="OVRSceneAnchor"/>s using
/// <see cref="OVRSceneAnchor.GetSceneAnchors(List{OVRSceneAnchor})"/>.
/// </remarks>
public Action SceneModelLoadedSuccessfully;
/// <summary>
/// This event fires when a query load the Scene Model returns no result. It can indicate that the,
/// user never used the Room Setup in the space they are in.
/// </summary>
public Action NoSceneModelToLoad;
/// <summary>
/// Unable to load the scene model because the user has not granted permission to use Scene.
/// </summary>
/// <remarks>
/// Apps that wish to use Scene must have "Spatial data" sharing permission. This is a runtime permission that
/// must be granted before loading the scene model. If the permission has not been granted, then calling
/// <see cref="LoadSceneModel"/> will result in this event.
///
/// The permission string is "com.oculus.permission.USE_SCENE". See Unity's
/// [Android Permission API](https://docs.unity3d.com/ScriptReference/Android.Permission.html) for more information
/// on interacting with permissions.
/// </remarks>
public event Action LoadSceneModelFailedPermissionNotGranted;
/// <summary>
/// This event will fire after the Room Setup successfully returns. It can be trapped to load the
/// scene Model.
/// </summary>
public Action SceneCaptureReturnedWithoutError;
/// <summary>
/// This event will fire if an error occurred while trying to send the user to Room Setup.
/// </summary>
public Action UnexpectedErrorWithSceneCapture;
/// <summary>
/// This event fires when the OVR Scene Manager detects a change in the room layout.
/// It indicates that the user performed Room Setup while the application was paused.
/// Upon receiving this event, user can call <see cref="LoadSceneModel" /> to reload the scene model.
/// </summary>
public Action NewSceneModelAvailable;
#endregion
/// <summary>
/// Represents the available classifications for each <see cref="OVRSceneAnchor"/>.
/// </summary>
/// <remarks>
/// Classifications can be useful for functionally describing the contents of an <see cref="OVRSceneRoom"/>.
/// By iterating all <see cref="OVRSceneAnchor"/>s and comparing their Classifications with those needed,
/// your application can determine whether a room meets its minimum requirements and/or can adjust its behavior
/// to fit the physical surroundings.
///
/// <see cref="OVRSceneManager"/> and associated classes are deprecated (v65), please use [MR Utility Kit](https://developer.oculus.com/documentation/unity/unity-mr-utility-kit-overview)" instead.
/// </remarks>
[Obsolete(DeprecationMessage)]
public static class Classification
{
/// <summary>
/// Represents an <see cref="OVRSceneAnchor"/> that is classified as a floor.
/// </summary>
public const string Floor = "FLOOR";
/// <summary>
/// Represents an <see cref="OVRSceneAnchor"/> that is classified as a ceiling.
/// </summary>
public const string Ceiling = "CEILING";
/// <summary>
/// Represents an <see cref="OVRSceneAnchor"/> that is classified as a wall face.
/// </summary>
public const string WallFace = "WALL_FACE";
/// <summary>
/// Represents an <see cref="OVRSceneAnchor"/> that is classified as a desk.
/// This label has been deprecated in favor of <see cref="Table"/>.
/// </summary>
[Obsolete("Deprecated. Use Table classification instead.")]
public const string Desk = "DESK";
/// <summary>
/// Represents an <see cref="OVRSceneAnchor"/> that is classified as a couch.
/// </summary>
public const string Couch = "COUCH";
/// <summary>
/// Represents an <see cref="OVRSceneAnchor"/> that is classified as a door frame.
/// </summary>
public const string DoorFrame = "DOOR_FRAME";
/// <summary>
/// Represents an <see cref="OVRSceneAnchor"/> that is classified as a window frame.
/// </summary>
public const string WindowFrame = "WINDOW_FRAME";
/// <summary>
/// Represents an <see cref="OVRSceneAnchor"/> that is classified as other.
/// </summary>
public const string Other = "OTHER";
/// <summary>
/// Represents an <see cref="OVRSceneAnchor"/> that is classified as a storage (e.g., cabinet, shelf).
/// </summary>
public const string Storage = "STORAGE";
/// <summary>
/// Represents an <see cref="OVRSceneAnchor"/> that is classified as a bed.
/// </summary>
public const string Bed = "BED";
/// <summary>
/// Represents an <see cref="OVRSceneAnchor"/> that is classified as a screen (e.g., TV, computer monitor).
/// </summary>
public const string Screen = "SCREEN";
/// <summary>
/// Represents an <see cref="OVRSceneAnchor"/> that is classified as a lamp.
/// </summary>
public const string Lamp = "LAMP";
/// <summary>
/// Represents an <see cref="OVRSceneAnchor"/> that is classified as a plant.
/// </summary>
public const string Plant = "PLANT";
/// <summary>
/// Represents an <see cref="OVRSceneAnchor"/> that is classified as a table.
/// </summary>
public const string Table = "TABLE";
/// <summary>
/// Represents an <see cref="OVRSceneAnchor"/> that is classified as wall art.
/// </summary>
public const string WallArt = "WALL_ART";
/// <summary>
/// Represents an <see cref="OVRSceneAnchor"/> that is classified as an invisible wall face.
/// All invisible wall faces are also classified as a <see cref="WallFace"/> in order to
/// provide backwards compatibility for apps that expect closed rooms to only consist of
/// wall faces, instead of a sequence composed of either invisible wall faces or wall faces.
/// </summary>
public const string InvisibleWallFace = "INVISIBLE_WALL_FACE";
/// <summary>
/// Represents an <see cref="OVRSceneAnchor"/> that is classified as a global mesh.
/// </summary>
public const string GlobalMesh = "GLOBAL_MESH";
/// <summary>
/// The list of all possible semantic labels that can be applied to an <see cref="OVRSceneAnchor"/>.
/// </summary>
/// <remarks>
/// <see cref="Set"/> exposes this list as a HashSet.
/// </remarks>
public static IReadOnlyList<string> List { get; } = new[]
{
Floor,
Ceiling,
WallFace,
#pragma warning disable CS0618 // Type or member is obsolete
Desk,
#pragma warning restore CS0618 // Type or member is obsolete
Couch,
DoorFrame,
WindowFrame,
Other,
Storage,
Bed,
Screen,
Lamp,
Plant,
Table,
WallArt,
InvisibleWallFace,
GlobalMesh,
};
/// <summary>
/// The set of possible semantic labels that can be applied to an <see cref="OVRSceneAnchor"/>.
/// </summary>
/// <remarks>
/// This is the same as <see cref="List"/> but allows for faster lookup.
/// </remarks>
public static HashSet<string> Set { get; } = new(List);
}
/// <summary>
/// A container for the set of <see cref="OVRSceneAnchor"/>s representing a room's Walls, Floor, and Ceiling as
/// <see cref="OVRScenePlane"/>s, which provide detailed geometry for each.
/// </summary>
/// <remarks>
/// <see cref="OVRSceneManager"/> and associated classes are deprecated (v65), please use [MR Utility Kit](https://developer.oculus.com/documentation/unity/unity-mr-utility-kit-overview)" instead.
/// </remarks>
[Obsolete("RoomLayoutInformation is obsoleted. For each room's layout information " +
"(floor, ceiling, walls) see " + nameof(OVRSceneRoom) + ".", false)]
public class RoomLayoutInformation
{
/// <summary>
/// The <see cref="OVRScenePlane"/> representing the floor of the room.
/// </summary>
public OVRScenePlane Floor;
/// <summary>
/// The <see cref="OVRScenePlane"/> representing the ceiling of the room.
/// </summary>
public OVRScenePlane Ceiling;
/// <summary>
/// The set of <see cref="OVRScenePlane"/> representing the walls of the room.
/// </summary>
public List<OVRScenePlane> Walls = new();
}
/// <summary>
/// Describes the room layout of a room in the scene model.
/// </summary>
[Obsolete(
"RoomLayout is obsoleted. For each room's layout information (floor, ceiling, walls) see " +
nameof(OVRSceneRoom) +
".",
false)]
public RoomLayoutInformation RoomLayout;
#region Private Vars
// We use this to store the request id when attempting to load the scene
private ulong _sceneCaptureRequestId = ulong.MaxValue;
private OVRCameraRig _cameraRig;
private int _sceneAnchorUpdateIndex;
private bool _hasLoadBeenRequested;
#endregion
#region Logging
internal struct LogForwarder
{
public void Log(string context, string message, GameObject gameObject = null) =>
Debug.Log($"[{context}] {message}", gameObject);
public void LogWarning(string context, string message, GameObject gameObject = null) =>
Debug.LogWarning($"[{context}] {message}", gameObject);
public void LogError(string context, string message, GameObject gameObject = null) =>
Debug.LogError($"[{context}] {message}", gameObject);
}
internal LogForwarder? Verbose => VerboseLogging ? new LogForwarder() : (LogForwarder?)null;
internal static class Development
{
[Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
public static void Log(string context, string message, GameObject gameObject = null) =>
Debug.Log($"[{context}] {message}", gameObject);
[Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
public static void LogWarning(string context, string message, GameObject gameObject = null) =>
Debug.LogWarning($"[{context}] {message}", gameObject);
[Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
public static void LogError(string context, string message, GameObject gameObject = null) =>
Debug.LogError($"[{context}] {message}", gameObject);
}
[Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
static void Log(string message, GameObject gameObject = null) =>
Development.Log(nameof(OVRSceneManager), message, gameObject);
[Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
static void LogWarning(string message, GameObject gameObject = null) =>
Development.LogWarning(nameof(OVRSceneManager), message, gameObject);
[Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
static void LogError(string message, GameObject gameObject = null) =>
Development.LogError(nameof(OVRSceneManager), message, gameObject);
#endregion
void Awake()
{
// Only allow one instance at runtime.
if (FindObjectsByType<OVRSceneManager>(FindObjectsSortMode.None).Length > 1)
{
new LogForwarder().LogError(nameof(OVRSceneManager),
$"Found multiple {nameof(OVRSceneManager)}s. Destroying '{name}'.");
enabled = false;
DestroyImmediate(this);
}
}
void Start()
{
OVRTelemetry.Start(OVRTelemetryConstants.Scene.MarkerId.UseOVRSceneManager)
.AddAnnotation(OVRTelemetryConstants.Scene.AnnotationType.UsingBasicPrefabs,
(PlanePrefab != null || VolumePrefab != null) ? "true" : "false")
.AddAnnotation(OVRTelemetryConstants.Scene.AnnotationType.UsingPrefabOverrides,
(PrefabOverrides.Count > 0) ? "true" : "false")
.AddAnnotation(OVRTelemetryConstants.Scene.AnnotationType.ActiveRoomsOnly,
ActiveRoomsOnly ? "true" : "false")
.Send();
}
private static void LogResult(OVRAnchor.FetchResult value)
{
if (((OVRPlugin.Result)value).IsSuccess())
{
Log($"xrDiscoverSpacesMETA completed successfully with result {value}.");
}
else
{
LogError($"xrDiscoverSpacesMETA failed with error {value}.");
}
}
internal static async OVRTask<bool> FetchAnchorsAsync<T>(List<OVRAnchor> anchors,
Action<List<OVRAnchor>, int> incrementalResultsCallback = null) where T : struct, IOVRAnchorComponent<T>
{
Log($"Fetching anchors of type {default(T).Type} using xrDiscoverSpacesMETA.");
var result = await OVRAnchor.FetchAnchorsAsync(anchors, new OVRAnchor.FetchOptions
{
SingleComponentType = typeof(T),
}, incrementalResultsCallback);
LogResult(result.Status);
return result.Success;
}
internal static async OVRTask<bool> FetchAnchorsAsync(IEnumerable<Guid> uuids, List<OVRAnchor> anchors)
{
Log($"Fetching {uuids.ToNonAlloc().GetCount()} anchors by UUID using xrDiscoverSpacesMETA.");
var result = await OVRAnchor.FetchAnchorsAsync(anchors, new OVRAnchor.FetchOptions
{
Uuids = uuids,
});
LogResult(result.Status);
return result.Success;
}
internal async void OnApplicationPause(bool isPaused)
{
// if we haven't loaded scene, we won't check anchor status
if (isPaused || !_hasLoadBeenRequested) return;
using (new OVRObjectPool.ListScope<OVRAnchor>(out var anchors))
{
var success = await FetchAnchorsAsync<OVRRoomLayout>(anchors);
if (!success)
{
Verbose?.Log(nameof(OVRSceneManager), "Failed to retrieve scene model information on resume.");
return;
}
// check whether room anchors have changed
foreach (var anchor in anchors)
{
if (!OVRSceneAnchor.SceneAnchors.ContainsKey(anchor.Uuid))
{
Verbose?.Log(nameof(OVRSceneManager),
$"Scene model changed. Invoking {nameof(NewSceneModelAvailable)} event.");
NewSceneModelAvailable?.Invoke();
break;
}
}
}
QueryForExistingAnchorsTransform();
}
private async void QueryForExistingAnchorsTransform()
{
using (new OVRObjectPool.ListScope<OVRAnchor>(out var anchors))
using (new OVRObjectPool.ListScope<Guid>(out var uuids))
{
foreach (var anchor in OVRSceneAnchor.SceneAnchorsList)
{
if (!anchor.Space.Valid || !anchor.IsTracked)
continue;
uuids.Add(anchor.Uuid);
}
if (uuids.Count > 0)
{
await FetchAnchorsAsync(uuids, anchors);
}
UpdateAllSceneAnchors();
}
}
/// <summary>
/// Loads the scene model
/// </summary>
/// <remarks>
/// The "scene model" consists of all the anchors (i.e., floor, ceiling, walls, and furniture) for all the rooms
/// defined during Space Setup.
///
/// When running on Quest, Scene is queried to retrieve the entities describing the Scene Model. In the Editor,
/// the Scene Model is loaded over Link.
/// </remarks>
/// <returns>Returns true if the query was successfully initiated.</returns>
public bool LoadSceneModel()
{
_hasLoadBeenRequested = true;
DestroyExistingAnchors();
var task = LoadSceneModelAsync();
if (!task.IsCompleted)
{
AwaitTask(task);
return true;
}
return InterpretResult(task.GetResult());
async void AwaitTask(OVRTask<LoadSceneModelResult> task) => InterpretResult(await task);
bool InterpretResult(LoadSceneModelResult result)
{
switch (result)
{
case LoadSceneModelResult.Success:
{
Log($"Scene model loaded successfully. Invoking {nameof(SceneModelLoadedSuccessfully)}");
SceneModelLoadedSuccessfully?.Invoke();
return true;
}
case LoadSceneModelResult.FailureScenePermissionNotGranted:
{
LogWarning($"Cannot retrieve anchors because {OVRPermissionsRequester.ScenePermission} has " +
$"not been granted. Invoking {nameof(LoadSceneModelFailedPermissionNotGranted)}",
gameObject);
LoadSceneModelFailedPermissionNotGranted?.Invoke();
// true because the query didn't fail
return true;
}
case LoadSceneModelResult.NoSceneModelToLoad:
{
LogWarning($"Although the app has {OVRPermissionsRequester.ScenePermission} permission, " +
$"loading the Scene definition yielded no result. Typically, this means the user has not " +
$"captured the room they are in yet. Alternatively, an internal error may be preventing " +
$"this app from accessing scene. Invoking {nameof(NoSceneModelToLoad)}");
NoSceneModelToLoad?.Invoke();
return true;
}
default: return false;
}
}
}
public enum LoadSceneModelResult
{
Success = 0,
NoSceneModelToLoad = 1,
FailureScenePermissionNotGranted = -1,
FailureUnexpectedError = -2,
}
internal struct Metrics
{
public int TotalRoomCount;
public int CandidateRoomCount;
public int Loaded;
public int Failed;
public int SkippedUserNotInRoom;
public int SkippedAlreadyInstantiated;
public static Metrics operator +(Metrics lhs, Metrics rhs) => new()
{
TotalRoomCount = lhs.TotalRoomCount + rhs.TotalRoomCount,
CandidateRoomCount = lhs.CandidateRoomCount + rhs.CandidateRoomCount,
Loaded = lhs.Loaded + rhs.Loaded,
Failed = lhs.Failed + rhs.Failed,
SkippedUserNotInRoom = lhs.SkippedUserNotInRoom + rhs.SkippedUserNotInRoom,
SkippedAlreadyInstantiated = lhs.SkippedAlreadyInstantiated + rhs.SkippedAlreadyInstantiated,
};
}
internal struct RoomLayoutUuids
{
public Guid Floor;
public Guid Ceiling;
public Guid[] Walls;
}
private async OVRTask<Metrics> ProcessBatch(List<OVRAnchor> rooms, int startingIndex)
{
Log($"Processing batch [{startingIndex}..{rooms.Count - 1}]", gameObject);
var metrics = new Metrics
{
// Rooms in this batch
TotalRoomCount = rooms.Count - startingIndex,
};
using (new OVRObjectPool.ListScope<OVRAnchor>(out var candidateRooms))
using (new OVRObjectPool.DictionaryScope<OVRAnchor, RoomLayoutUuids>(out var layoutUuids))
{
for (var i = startingIndex; i < rooms.Count; i++)
{
var room = rooms[i];
var layout = default(RoomLayoutUuids);
if (room.GetComponent<OVRRoomLayout>()
.TryGetRoomLayout(out layout.Ceiling, out layout.Floor, out layout.Walls))
{
layoutUuids.Add(room, layout);
candidateRooms.Add(room);
}
else
{
LogError($"Unable to retrieve room layout information for {room.Uuid}. Ignoring.", gameObject);
metrics.Failed++;
}
}
metrics.CandidateRoomCount = candidateRooms.Count;
if (candidateRooms.Count == 0)
{
return metrics;
}
if (ActiveRoomsOnly)
{
Log($"Filtering inactive rooms.");
LoadSceneModelResult result;
(result, metrics.SkippedUserNotInRoom) = await FilterByActiveRoom(candidateRooms, layoutUuids);
if ((int)result < 0)
{
metrics.Failed += metrics.CandidateRoomCount;
return metrics;
}
if (metrics.SkippedUserNotInRoom > 0)
{
LogWarning($"{metrics.SkippedUserNotInRoom} of {metrics.CandidateRoomCount} candidate " +
$"room(s) were ignored because the user is not in them.");
}
// We must have filtered them all out
if (candidateRooms.Count == 0)
{
return metrics;
}
}
using (new OVRObjectPool.ListScope<bool>(out var taskResults))
using (new OVRObjectPool.ListScope<OVRTask<bool>>(out var tasks))
{
foreach (var room in candidateRooms)
{
// Skip pre-existing rooms
if (OVRSceneAnchor.SceneAnchors.TryGetValue(room.Uuid, out var sceneAnchor))
{
LogWarning($"Skipping {sceneAnchor.name} because it has already been instantiated.");
sceneAnchor.IsTracked = true;
metrics.SkippedAlreadyInstantiated++;
continue;
}
var layout = layoutUuids[room];
var roomGameObject = new GameObject($"Room {room.Uuid}");
roomGameObject.transform.parent = _initialAnchorParent;
sceneAnchor = roomGameObject.AddComponent<OVRSceneAnchor>();
sceneAnchor.Initialize(room);
var sceneRoom = roomGameObject.AddComponent<OVRSceneRoom>();
tasks.Add(sceneRoom.LoadRoom(layout.Floor, layout.Ceiling, layout.Walls));
}
await OVRTask.WhenAll(tasks, taskResults);
foreach (var loadSuccessful in taskResults)
{
if (loadSuccessful)
{
metrics.Loaded++;
}
else
{
metrics.Failed++;
}
}
}
}
return metrics;
}
private async OVRTask<LoadSceneModelResult> LoadSceneModelAsync()
{
if (!Permission.HasUserAuthorizedPermission(OVRPermissionsRequester.ScenePermission))
{
return LoadSceneModelResult.FailureScenePermissionNotGranted;
}
using (new OVRObjectPool.ListScope<Metrics>(out var taskResults))
using (new OVRObjectPool.ListScope<OVRTask<Metrics>>(out var tasks))
using (new OVRObjectPool.ListScope<OVRAnchor>(out var rooms))
{
var result = await FetchAnchorsAsync<OVRRoomLayout>(rooms, (rooms, startingIndex) =>
{
tasks.Add(ProcessBatch(rooms, startingIndex));
});
// Wait for all batches to complete
await OVRTask.WhenAll(tasks, taskResults);
if (!result)
{
LogError($"Unable to query for rooms.", gameObject);
return LoadSceneModelResult.FailureUnexpectedError;
}
var combinedMetrics = default(Metrics);
foreach (var batchMetrics in taskResults)
{
combinedMetrics += batchMetrics;
}
Log($"{nameof(LoadSceneModelAsync)} Report:\n" +
$"\t{combinedMetrics.TotalRoomCount} total rooms\n" +
$"\t{combinedMetrics.CandidateRoomCount} candidate rooms\n" +
$"\t{combinedMetrics.Loaded} loaded\n" +
$"\t{combinedMetrics.Failed} failed\n" +
$"\t{combinedMetrics.SkippedAlreadyInstantiated} skipped because the room is already instantiated\n" +
$"\t{combinedMetrics.SkippedUserNotInRoom} skipped because the user is not in the room",
gameObject);
if (combinedMetrics.Loaded > 0)
{
#pragma warning disable CS0618 // Type or member is obsolete
RoomLayout = GetRoomLayoutInformation();
#pragma warning restore CS0618
Log($"{LoadSceneModelResult.Success}: Successfully loaded {combinedMetrics.Loaded} room(s)");
return LoadSceneModelResult.Success;
}
if (combinedMetrics.SkippedAlreadyInstantiated > 0)
{
Log($"{LoadSceneModelResult.Success}: Did not load any new rooms, but there are {combinedMetrics.SkippedAlreadyInstantiated} room(s) that were loaded previously.");
return LoadSceneModelResult.Success;
}
if (combinedMetrics.SkippedUserNotInRoom > 0)
{
LogWarning($" {LoadSceneModelResult.NoSceneModelToLoad}: There are {combinedMetrics.TotalRoomCount} room(s) but the user is not in any of them.");
return LoadSceneModelResult.NoSceneModelToLoad;
}
if (combinedMetrics.Failed > 0)
{
LogError($" {LoadSceneModelResult.FailureUnexpectedError}: Failed to load {combinedMetrics.Failed} room(s).");
return LoadSceneModelResult.FailureUnexpectedError;
}
// Nothing was loaded, skipped, or failed, and we have permission, so there must not be a scene model
Log($" {LoadSceneModelResult.NoSceneModelToLoad}: Query succeeded and {OVRPermissionsRequester.ScenePermission} permission has been granted, but there are no results.");
return LoadSceneModelResult.NoSceneModelToLoad;
}
}
private static async OVRTask<(LoadSceneModelResult, int)> FilterByActiveRoom(List<OVRAnchor> rooms,
Dictionary<OVRAnchor, RoomLayoutUuids> layouts)
{
rooms.Clear();
var skipped = 0;
var userPosition = OVRPlugin.GetNodePose(OVRPlugin.Node.EyeCenter, OVRPlugin.Step.Render)
.Position
.FromVector3f();
using (new OVRObjectPool.ListScope<OVRAnchor>(out var floorAndCeilingAnchors))
using (new OVRObjectPool.ListScope<Guid>(out var floorAndCeilingUuids))
{
foreach (var layout in layouts.Values)
{
floorAndCeilingUuids.Add(layout.Ceiling);
floorAndCeilingUuids.Add(layout.Floor);
}
var result = await FetchAnchorsAsync(floorAndCeilingUuids, floorAndCeilingAnchors);
if (!result)
{
Development.LogError(nameof(OVRSceneManager), $"Unable to load floor and ceiling anchors.");
return (LoadSceneModelResult.FailureUnexpectedError, 0);
}
using (new OVRObjectPool.ListScope<bool>(out var results))
using (new OVRObjectPool.ListScope<OVRTask<bool>>(out var tasks))
{
foreach (var anchor in floorAndCeilingAnchors)
{
if (anchor.TryGetComponent<OVRLocatable>(out var locatable))
{
tasks.Add(locatable.SetEnabledAsync(true));
}
}
await OVRTask.WhenAll(tasks, results);
}
using (new OVRObjectPool.DictionaryScope<Guid, OVRAnchor>(out var uuidToAnchor))
{
foreach (var anchor in floorAndCeilingAnchors)
{
uuidToAnchor.Add(anchor.Uuid, anchor);
}
foreach (var (room, layout) in layouts)
{
if (uuidToAnchor.TryGetValue(layout.Floor, out var floor) &&
uuidToAnchor.TryGetValue(layout.Ceiling, out var ceiling) &&
IsUserInRoom(userPosition, floor: floor, ceiling: ceiling))
{
rooms.Add(room);
}
else
{
skipped++;
}
}
}
}
return (LoadSceneModelResult.Success, skipped);
}
#region Loading active room(s)
private static bool IsUserInRoom(Vector3 userPosition, OVRAnchor floor, OVRAnchor ceiling)
{
// Get floor anchor's pose
if (!OVRPlugin.TryLocateSpace(floor.Handle, OVRPlugin.GetTrackingOriginType(), out var floorPose))
{
Development.LogError(nameof(OVRSceneManager), $"Could not locate floor anchor {floor.Uuid}");
return false;
}
// Get ceiling anchor's pose
if (!OVRPlugin.TryLocateSpace(ceiling.Handle, OVRPlugin.GetTrackingOriginType(), out var ceilingPose))
{
Development.LogError(nameof(OVRSceneManager), $"Could not locate ceiling anchor {ceiling.Uuid}");
return false;
}
// Get room boundary vertices (assumes floor and ceiling have the same 2d boundary)
if (!OVRPlugin.GetSpaceBoundary2DCount(floor.Handle, out var count))
{
Development.LogWarning(nameof(OVRSceneManager), $"Could not get floor boundary {floor.Uuid}");
return false;
}
using var boundaryVertices = new NativeArray<Vector2>(count, Allocator.Temp);
if (!OVRPlugin.GetSpaceBoundary2D(floor.Handle, boundaryVertices))
return false;
if (userPosition.y < floorPose.Position.y)
return false;
if (userPosition.y > ceilingPose.Position.y)
return false;
// Perform location check
var offsetWithFloor = userPosition - floorPose.Position.FromVector3f();
var userPositionInRoom = Quaternion.Inverse(floorPose.Orientation.FromQuatf()) * offsetWithFloor;
return PointInPolygon2D(boundaryVertices, userPositionInRoom);
}
#endregion
private void DestroyExistingAnchors()
{
// Remove all the scene entities in memory. Update with scene entities from new query.
using (new OVRObjectPool.ListScope<OVRSceneAnchor>(out var anchors))
{
OVRSceneAnchor.GetSceneAnchors(anchors);
foreach (var sceneAnchor in anchors)
{
Destroy(sceneAnchor.gameObject);
}
}
#pragma warning disable CS0618 // Type or member is obsolete
RoomLayout = null;
#pragma warning restore CS0618
}
/// <summary>
/// Requests scene capture from the Room Setup.
/// </summary>
/// <returns>Returns true if scene capture succeeded, otherwise false.</returns>
public bool RequestSceneCapture()
{
#if !UNITY_EDITOR
bool result = OVRPlugin.RequestSceneCapture(out _sceneCaptureRequestId);
if (!result)
{
UnexpectedErrorWithSceneCapture?.Invoke();
}
// When a scene capture has been successfuly requested, silent fall through as it does not imply a successful scene capture
return result;
#else
Development.LogWarning(nameof(OVRSceneManager),
"Scene Capture does not work over Link.\n"
+ "Please capture a scene with the HMD in standalone mode, then access the scene model over Link.");
UnexpectedErrorWithSceneCapture?.Invoke();
return false;
#endif
}
/// <summary>
/// Check if a room setup exists with specified anchors classifications.
/// </summary>
/// <param name="requestedAnchorClassifications">Anchors classifications to check.</param>
/// <returns>OVRTask that gives a boolean answer if the room setup exists upon completion.</returns>
public OVRTask<bool> DoesRoomSetupExist(IEnumerable<string> requestedAnchorClassifications)
{
var task = OVRTask.FromGuid<bool>(Guid.NewGuid());
CheckIfClassificationsAreValid(requestedAnchorClassifications);
using (new OVRObjectPool.ListScope<OVRAnchor>(out var roomAnchors))
{
var roomsTask = FetchAnchorsAsync<OVRRoomLayout>(roomAnchors);
roomsTask.ContinueWith((result, anchors) => CheckClassificationsInRooms(result, anchors, requestedAnchorClassifications, task), roomAnchors);
}
return task;
}
private static void CheckIfClassificationsAreValid(IEnumerable<string> requestedAnchorClassifications)
{
if (requestedAnchorClassifications == null)
{
throw new ArgumentNullException(nameof(requestedAnchorClassifications));
}
foreach (var classification in requestedAnchorClassifications)
{
if (!Classification.Set.Contains(classification))
{
throw new ArgumentException(
$"{nameof(requestedAnchorClassifications)} contains invalid anchor {nameof(Classification)} {classification}.");
}
}
}
private static void GetUuidsToQuery(OVRAnchor anchor, HashSet<Guid> uuidsToQuery)
{
if (anchor.TryGetComponent<OVRAnchorContainer>(out var container))
{
foreach (var uuid in container.Uuids)
{
uuidsToQuery.Add(uuid);
}
}
}
private static void CheckClassificationsInRooms(bool success, List<OVRAnchor> rooms, IEnumerable<string> requestedAnchorClassifications, OVRTask<bool> task)
{
if (!success)
{
Development.Log(nameof(OVRSceneManager),
$"{nameof(OVRAnchor.FetchAnchorsAsync)} failed on {nameof(DoesRoomSetupExist)}() request to fetch room anchors.");
return;
}
using (new OVRObjectPool.HashSetScope<Guid>(out var uuidsToQuery))
using (new OVRObjectPool.ListScope<Guid>(out var anchorUuids))
{
for (int i = 0; i < rooms.Count; i++)
{
GetUuidsToQuery(rooms[i], uuidsToQuery);
anchorUuids.AddRange(uuidsToQuery);
uuidsToQuery.Clear();
}
using (new OVRObjectPool.ListScope<OVRAnchor>(out var roomAnchors))
{
FetchAnchorsAsync(anchorUuids, roomAnchors)
.ContinueWith(result => CheckIfAnchorsContainClassifications(result, roomAnchors, requestedAnchorClassifications, task));
}
}
}
private static void CheckIfAnchorsContainClassifications(bool success, List<OVRAnchor> roomAnchors, IEnumerable<string> requestedAnchorClassifications, OVRTask<bool> task)
{
if (!success)
{
Development.Log(nameof(OVRSceneManager),
$"{nameof(OVRAnchor.FetchAnchorsAsync)} failed on {nameof(DoesRoomSetupExist)}() request to fetch anchors in rooms.");
return;
}
using (new OVRObjectPool.ListScope<string>(out var labels))
{
CollectLabelsFromAnchors(roomAnchors, labels);
foreach (var classification in requestedAnchorClassifications)
{
var labelIndex = labels.IndexOf(classification);
if (labelIndex >= 0)
{
labels.RemoveAt(labelIndex);
}
else
{
task.SetResult(false);
return;
}
}
}
task.SetResult(true);
}
private static void CollectLabelsFromAnchors(List<OVRAnchor> anchors, List<string> labels)
{
for (int i = 0; i < anchors.Count; i++)
{
var anchor = anchors[i];
if (anchor.TryGetComponent<OVRSemanticLabels>(out var classification))
{
labels.AddRange(classification.Labels.Split(OVRSemanticClassification.LabelSeparator));
}
}
}
private static void OnTrackingSpaceChanged(Transform trackingSpace)
{
// Tracking space changed, update all scene anchors using their cache
UpdateAllSceneAnchors();
}
private void Update()
{
UpdateSomeSceneAnchors();
}
private static void UpdateAllSceneAnchors()
{
foreach (var sceneAnchor in OVRSceneAnchor.SceneAnchors.Values)
{
sceneAnchor.TryUpdateTransform(useCache: true);
if (sceneAnchor.TryGetComponent(out OVRScenePlane plane))
{
plane.UpdateTransform();
plane.RequestBoundary();
}
if (sceneAnchor.TryGetComponent(out OVRSceneVolume volume))
{
volume.UpdateTransform();
}
}
}
private void UpdateSomeSceneAnchors()
{
for (var i = 0; i < Math.Min(OVRSceneAnchor.SceneAnchorsList.Count, MaxSceneAnchorUpdatesPerFrame); i++)
{
_sceneAnchorUpdateIndex %= OVRSceneAnchor.SceneAnchorsList.Count;
var anchor = OVRSceneAnchor.SceneAnchorsList[_sceneAnchorUpdateIndex++];
anchor.TryUpdateTransform(useCache: false);
}
}
#pragma warning disable CS0618 // Type or member is obsolete
private RoomLayoutInformation GetRoomLayoutInformation()
{
var roomLayout = new RoomLayoutInformation();
#pragma warning restore CS0618 // Type or member is obsolete
if (OVRSceneRoom.SceneRoomsList.Count > 0)
{
roomLayout.Floor = OVRSceneRoom.SceneRoomsList[0].Floor;
roomLayout.Ceiling = OVRSceneRoom.SceneRoomsList[0].Ceiling;
roomLayout.Walls.Clear();
roomLayout.Walls.AddRange(OVRSceneRoom.SceneRoomsList[0].Walls);
}
return roomLayout;
}
private void OnEnable()
{
// Bind events
OVRManager.SceneCaptureComplete += OVRManager_SceneCaptureComplete;
if (OVRManager.display != null)
{
OVRManager.display.RecenteredPose += UpdateAllSceneAnchors;
}
if (!_cameraRig)
{
_cameraRig = FindAnyObjectByType<OVRCameraRig>();
}
if (_cameraRig)
{
_cameraRig.TrackingSpaceChanged += OnTrackingSpaceChanged;
}
}
private void OnDisable()
{
// Unbind events
OVRManager.SceneCaptureComplete -= OVRManager_SceneCaptureComplete;
if (OVRManager.display != null)
{
OVRManager.display.RecenteredPose -= UpdateAllSceneAnchors;
}
if (_cameraRig)
{
_cameraRig.TrackingSpaceChanged -= OnTrackingSpaceChanged;
}
}
/// <summary>
/// Determines if a point is inside of a 2d polygon.
/// </summary>
/// <param name="boundaryVertices">The vertices that make up the bounds of the polygon</param>
/// <param name="target">The target point to test</param>
/// <returns>True if the point is inside the polygon, false otherwise</returns>
internal static bool PointInPolygon2D(NativeArray<Vector2> boundaryVertices, Vector2 target)
{
if (boundaryVertices.Length < 3)
return false;
int collision = 0;
var x = target.x;
var y = target.y;
for (int i = 0; i < boundaryVertices.Length; i++)
{
var x1 = boundaryVertices[i].x;
var y1 = boundaryVertices[i].y;
var x2 = boundaryVertices[(i + 1) % boundaryVertices.Length].x;
var y2 = boundaryVertices[(i + 1) % boundaryVertices.Length].y;
if (y < y1 != y < y2 &&
x < x1 + ((y - y1) / (y2 - y1)) * (x2 - x1))
{
collision += (y1 < y2) ? 1 : -1;
}
}
return collision != 0;
}
#region Action callbacks
private void OVRManager_SceneCaptureComplete(UInt64 requestId, bool result)
{
if (requestId != _sceneCaptureRequestId)
{
Verbose?.LogWarning(nameof(OVRSceneManager),
$"Scene Room Setup with requestId: [{requestId}] was ignored, as it was not issued by this Scene Load request.");
return;
}
Development.Log(nameof(OVRSceneManager),
$"{nameof(OVRManager_SceneCaptureComplete)}() requestId: [{requestId}] result: [{result}]");
if (result)
{
// Either the user created a room, or they confirmed that the existing room is up to date. We can now load it.
Development.Log(nameof(OVRSceneManager),
$"The Room Setup returned without errors. Invoking {nameof(SceneCaptureReturnedWithoutError)}.");
SceneCaptureReturnedWithoutError?.Invoke();
}
else
{
Development.LogError(nameof(OVRSceneManager),
$"An error occurred when sending the user to the Room Setup. Invoking {nameof(UnexpectedErrorWithSceneCapture)}.");
UnexpectedErrorWithSceneCapture?.Invoke();
}
}
internal OVRSceneAnchor InstantiateSceneAnchor(OVRAnchor anchor, OVRSceneAnchor prefab)
{
var space = (OVRSpace)anchor.Handle;
var uuid = anchor.Uuid;
// Query for the semantic classification of the object
var hasSemanticLabels = OVRPlugin.GetSpaceSemanticLabels(space, out var labelString);
var labels = hasSemanticLabels
? labelString.Split(',')
: Array.Empty<string>();
// Search the prefab override for a matching label, and if found override the prefab
if (PrefabOverrides.Count > 0)
{
foreach (var label in labels)
{
// Skip empty labels
if (string.IsNullOrEmpty(label)) continue;
// Search the prefab override for an entry matching the label
foreach (var @override in PrefabOverrides)
{
if (@override.ClassificationLabel == label)
{
prefab = @override.Prefab;
break;
}
}
}
}
// This can occur if neither the prefab nor any matching override prefab is set in the inspector
if (prefab == null)
{
Verbose?.Log(nameof(OVRSceneManager),
$"No prefab was provided for space: [{space}]"
+ (labels.Length > 0 ? $" with semantic label {labels[0]}" : ""));
return null;
}
var sceneAnchor = Instantiate(prefab, Vector3.zero, Quaternion.identity, _initialAnchorParent);
sceneAnchor.gameObject.SetActive(true);
sceneAnchor.Initialize(anchor);
return sceneAnchor;
}
#endregion
}