/*
* 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 UnityEngine;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Unity.Collections;
using UnityEngine.Serialization;
using Debug = UnityEngine.Debug;
///
/// A manager for s created using the Room Setup feature.
///
[HelpURL("https://developer.oculus.com/reference/unity/latest/class_o_v_r_scene_manager")]
public class OVRSceneManager : MonoBehaviour
{
///
/// 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, will
/// be used instead. If null, no object will be instantiated,
/// unless a prefab override is provided.
///
[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;
///
/// 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.
///
[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;
///
/// 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.
///
[FormerlySerializedAs("prefabOverrides")]
[Tooltip("Overrides the instantiation of the generic Plane/Volume prefabs with specialized ones.")]
public List PrefabOverrides = new List();
///
/// If True, the will present the room(s) the user is currently in.
/// Otherwise, the will present all the room(s) detected by the system at
/// the time of execution.
///
///
/// No scene room will be presented if this value set to True and a user is not in any room(s)
/// during the execution.
///
[Tooltip("Scene manager will only present the room(s) the user is currently in.")]
public bool ActiveRoomsOnly = true;
///
/// When true, verbose debug logs will be emitted.
///
[FormerlySerializedAs("verboseLogging")]
[Tooltip("When enabled, verbose debug logs will be emitted.")]
public bool VerboseLogging;
///
/// The maximum number of scene anchors that will be updated each frame.
///
[Tooltip("The maximum number of scene anchors that will be updated each frame.")]
public int MaxSceneAnchorUpdatesPerFrame = 3;
///
/// The parent transform to which each new or
/// will be parented upon instantiation.
///
///
/// if null, (s) instantiated by will have no parent, and
/// (s) will have either a as their parent or null, that is
/// they will be instantiated at the scene root. If non-null, (s) that do not
/// belong to any , and (s) along with its child
/// (s) will be parented to .
///
/// Changing this value does not affect existing (s) or (s).
///
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
///
/// This event fires when the OVR Scene Manager has correctly loaded the scene definition and
/// instantiated the prefabs for the planes and volumes. Trap it to know that the logic of the
/// experience can now continue.
///
public Action SceneModelLoadedSuccessfully;
///
/// 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.
///
public Action NoSceneModelToLoad;
///
/// This event will fire after the Room Setup successfully returns. It can be trapped to load the
/// scene Model.
///
public Action SceneCaptureReturnedWithoutError;
///
/// This event will fire if an error occurred while trying to send the user to Room Setup.
///
public Action UnexpectedErrorWithSceneCapture;
///
/// 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 to reload the scene model.
///
public Action NewSceneModelAvailable;
#endregion
///
/// Represents the available classifications for each .
///
public static class Classification
{
///
/// Represents an that is classified as a floor.
///
public const string Floor = "FLOOR";
///
/// Represents an that is classified as a ceiling.
///
public const string Ceiling = "CEILING";
///
/// Represents an that is classified as a wall face.
///
public const string WallFace = "WALL_FACE";
///
/// Represents an that is classified as a desk.
/// This label has been deprecated in favor of .
///
[Obsolete("Deprecated. Use Table classification instead.")]
public const string Desk = "DESK";
///
/// Represents an that is classified as a couch.
///
public const string Couch = "COUCH";
///
/// Represents an that is classified as a door frame.
///
public const string DoorFrame = "DOOR_FRAME";
///
/// Represents an that is classified as a window frame.
///
public const string WindowFrame = "WINDOW_FRAME";
///
/// Represents an that is classified as other.
///
public const string Other = "OTHER";
///
/// Represents an that is classified as a storage (e.g., cabinet, shelf).
///
public const string Storage = "STORAGE";
///
/// Represents an that is classified as a bed.
///
public const string Bed = "BED";
///
/// Represents an that is classified as a screen (e.g., TV, computer monitor).
///
public const string Screen = "SCREEN";
///
/// Represents an that is classified as a lamp.
///
public const string Lamp = "LAMP";
///
/// Represents an that is classified as a plant.
///
public const string Plant = "PLANT";
///
/// Represents an that is classified as a table.
///
public const string Table = "TABLE";
///
/// Represents an that is classified as wall art.
///
public const string WallArt = "WALL_ART";
///
/// Represents an that is classified as an invisible wall face.
/// All invisible wall faces are also classified as a 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.
///
public const string InvisibleWallFace = "INVISIBLE_WALL_FACE";
///
/// Represents an that is classified as a global mesh.
///
public const string GlobalMesh = "GLOBAL_MESH";
///
/// The list of possible semantic labels.
///
public static IReadOnlyList 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,
};
}
///
/// A container for the set of s representing a room.
///
[Obsolete("RoomLayoutInformation is obsoleted. For each room's layout information " +
"(floor, ceiling, walls) see " + nameof(OVRSceneRoom) + ".", false)]
public class RoomLayoutInformation
{
///
/// The representing the floor of the room.
///
public OVRScenePlane Floor;
///
/// The representing the ceiling of the room.
///
public OVRScenePlane Ceiling;
///
/// The set of representing the walls of the room.
///
public List Walls = new List();
}
///
/// Describes the room layout of a room in the scene model.
///
[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 UInt64 _sceneCaptureRequestId = UInt64.MaxValue;
private OVRCameraRig _cameraRig;
private int _sceneAnchorUpdateIndex;
private int _roomCounter;
private Action> _onAnchorsFetchCompleted;
private bool _hasLoadedScene = false;
private Action _onFloorAnchorsFetchCompleted;
private Action _onFloorAnchorLocalizationCompleted;
private List _floorAnchors = OVRObjectPool.Get>();
private readonly HashSet _pendingLocatable = OVRObjectPool.Get>();
private Dictionary _roomAndFloorPairs = OVRObjectPool.Get>();
private List _roomLayoutAnchors = new List();
#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);
}
#endregion
void Awake()
{
// Only allow one instance at runtime.
if (FindObjectsOfType().Length > 1)
{
new LogForwarder().LogError(nameof(OVRSceneManager),
$"Found multiple {nameof(OVRSceneManager)}s. Destroying '{name}'.");
enabled = false;
DestroyImmediate(this);
}
_onAnchorsFetchCompleted = OnAnchorsFetchCompleted;
_onFloorAnchorsFetchCompleted = OnFloorAnchorsFetchCompleted;
_onFloorAnchorLocalizationCompleted = OnFloorAnchorLocalizationCompleted;
}
internal async void OnApplicationPause(bool isPaused)
{
// if we haven't loaded scene, we won't check anchor status
if (isPaused || !_hasLoadedScene) return;
using (new OVRObjectPool.ListScope(out var anchors))
{
var success = await OVRAnchor.FetchAnchorsAsync(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(out var anchors))
using (new OVRObjectPool.ListScope(out var uuids))
{
foreach (var anchor in OVRSceneAnchor.SceneAnchorsList)
{
if (!anchor.Space.Valid || !anchor.IsTracked)
continue;
uuids.Add(anchor.Uuid);
}
if (uuids.Any())
await OVRAnchor.FetchAnchorsAsync(uuids, anchors);
UpdateAllSceneAnchors();
}
}
///
/// Loads the scene model from the Room 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.
///
/// Returns true if the query was successfully registered
public bool LoadSceneModel()
{
_hasLoadedScene = true;
DestroyExistingAnchors();
var anchors = OVRObjectPool.List();
var task = OVRAnchor.FetchAnchorsAsync(anchors);
task.ContinueWith(_onAnchorsFetchCompleted, anchors);
return task.IsPending;
}
private void OnAnchorsFetchCompleted(bool success, List roomLayoutAnchors)
{
if (success)
{
if (roomLayoutAnchors.Any())
{
if (ActiveRoomsOnly)
InstantiateActiveRooms(roomLayoutAnchors);
else
InstantiateSceneRooms(roomLayoutAnchors);
}
else
{
if (VerboseLogging)
{
var scenePermission = OVRPermissionsRequester.Permission.Scene;
if (!OVRPermissionsRequester.IsPermissionGranted(scenePermission))
{
Verbose?.LogWarning(nameof(OVRSceneManager),
$"Cannot retrieve anchors as {scenePermission} hasn't been granted.",
gameObject);
}
}
Development.LogWarning(nameof(OVRSceneManager),
"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();
}
}
OVRObjectPool.Return(roomLayoutAnchors);
}
#region Loading active room(s)
private void InstantiateActiveRooms(List roomLayoutAnchors)
{
_floorAnchors.Clear();
_roomAndFloorPairs.Clear();
_pendingLocatable.Clear();
using (new OVRObjectPool.ListScope(out var floorUuids))
{
// Get all floors
foreach (var roomLayoutAnchor in roomLayoutAnchors)
{
if (!roomLayoutAnchor.TryGetComponent(out OVRRoomLayout roomLayout) ||
!roomLayout.TryGetRoomLayout(out _, out var floorUuid, out _))
continue;
floorUuids.Add(floorUuid);
_roomAndFloorPairs[floorUuid] = roomLayoutAnchor;
}
// Make query by uuids to fetch floor anchors
OVRAnchor.FetchAnchorsAsync(floorUuids, _floorAnchors).ContinueWith(_onFloorAnchorsFetchCompleted);
}
roomLayoutAnchors.Clear();
}
private void OnFloorAnchorsFetchCompleted(bool success)
{
if (!success) return;
_roomLayoutAnchors.Clear();
foreach (var floorAnchor in _floorAnchors)
{
// Make anchors locatable for Pose
if (!floorAnchor.TryGetComponent(out OVRLocatable locatable))
{
continue;
}
if (locatable.IsEnabled)
{
LocateUserInRoom(floorAnchor);
continue;
}
locatable.SetEnabledAsync(true).ContinueWith(_onFloorAnchorLocalizationCompleted, floorAnchor);
_pendingLocatable.Add(floorAnchor.Uuid);
}
}
private void OnFloorAnchorLocalizationCompleted(bool success, OVRAnchor anchor)
{
if (!_pendingLocatable.Contains(anchor.Uuid))
return;
_pendingLocatable.Remove(anchor.Uuid);
if (!success) return;
LocateUserInRoom(anchor);
}
private void LocateUserInRoom(OVRAnchor anchor)
{
var space = anchor.Handle;
var uuid = anchor.Uuid;
// Get floor anchor's pose
if (!OVRPlugin.TryLocateSpace(space, OVRPlugin.GetTrackingOriginType(), out var pose))
{
return;
}
// Get room boundary vertices
if (!OVRPlugin.GetSpaceBoundary2DCount(space, out var count))
return;
using var boundaryVertices = new NativeArray(count, Allocator.Temp);
if (!OVRPlugin.GetSpaceBoundary2D(space, boundaryVertices))
return;
// Perform location check
var playerPosition = OVRPlugin.GetNodePose(OVRPlugin.Node.EyeCenter, OVRPlugin.Step.Render).Position.FromVector3f();
var offsetWithFloor = playerPosition - pose.Position.FromVector3f();
playerPosition = Quaternion.Inverse(pose.Orientation.FromQuatf()) * offsetWithFloor;
if (PointInPolygon2D(boundaryVertices, playerPosition) &&
_roomAndFloorPairs.TryGetValue(uuid, out var roomAnchor))
{
_roomLayoutAnchors.Add(roomAnchor);
}
if (!_roomLayoutAnchors.Any())
{
Verbose?.Log(nameof(OVRSceneManager), "User is not present in any room(s).");
return;
}
// Instantiate room(s)
if (!_pendingLocatable.Any())
{
InstantiateSceneRooms(_roomLayoutAnchors);
}
}
#endregion
private void InstantiateSceneRooms(List roomLayoutAnchors)
{
_roomCounter = roomLayoutAnchors.Count;
foreach (var roomAnchor in roomLayoutAnchors)
{
// Check if anchor already exists
if (OVRSceneAnchor.SceneAnchors.TryGetValue(roomAnchor.Uuid, out var sceneAnchor))
{
sceneAnchor.IsTracked = true;
continue;
}
if (!(roomAnchor.TryGetComponent(out OVRRoomLayout roomLayoutComponent) &&
roomLayoutComponent.IsEnabled))
{
continue;
}
var roomGO = new GameObject("Room " + roomAnchor.Uuid);
roomGO.transform.parent = _initialAnchorParent;
sceneAnchor = roomGO.AddComponent();
sceneAnchor.Initialize(roomAnchor);
var sceneRoom = roomGO.AddComponent();
sceneRoom.LoadRoom();
}
}
internal void OnSceneRoomLoadCompleted()
{
if (--_roomCounter > 0) return;
#pragma warning disable CS0618 // Type or member is obsolete
// room layout needs to be defined before invoking the
// event else we may have access to a null property
RoomLayout = GetRoomLayoutInformation();
#pragma warning restore CS0618 // Type or member is obsolete
SceneModelLoadedSuccessfully?.Invoke();
Verbose?.Log(nameof(OVRSceneManager), "Scene model loading completed.");
}
private void DestroyExistingAnchors()
{
// Remove all the scene entities in memory. Update with scene entities from new query.
var anchors = new List();
OVRSceneAnchor.GetSceneAnchors(anchors);
foreach (var sceneAnchor in anchors)
{
Destroy(sceneAnchor.gameObject);
}
}
///
/// Requests scene capture from the Room Setup.
///
/// Returns true if scene capture succeeded, otherwise false.
public bool RequestSceneCapture() => RequestSceneCapture("");
///
/// Requests scene capture with specified types of
///
/// A list of .
/// Returns true if scene capture succeeded, otherwise false.
public bool RequestSceneCapture(IEnumerable requestedAnchorClassifications)
{
CheckIfClassificationsAreValid(requestedAnchorClassifications);
return RequestSceneCapture(String.Join(OVRSemanticClassification.LabelSeparator.ToString(), requestedAnchorClassifications));
}
///
/// Check if a room setup exists with specified anchors classifications.
///
/// Anchors classifications to check.
/// OVRTask that gives a boolean answer if the room setup exists upon completion.
public OVRTask DoesRoomSetupExist(IEnumerable requestedAnchorClassifications)
{
var task = OVRTask.FromGuid(Guid.NewGuid());
CheckIfClassificationsAreValid(requestedAnchorClassifications);
using (new OVRObjectPool.ListScope(out var roomAnchors))
{
var roomsTask = OVRAnchor.FetchAnchorsAsync(roomAnchors);
roomsTask.ContinueWith((result, anchors) => CheckClassificationsInRooms(result, anchors, requestedAnchorClassifications, task), roomAnchors);
}
return task;
}
private static void CheckIfClassificationsAreValid(IEnumerable requestedAnchorClassifications)
{
if (requestedAnchorClassifications == null)
{
throw new ArgumentNullException(nameof(requestedAnchorClassifications));
}
foreach (var classification in requestedAnchorClassifications)
{
if (!Classification.List.Contains(classification))
{
throw new ArgumentException(
$"{nameof(requestedAnchorClassifications)} contains invalid anchor {nameof(Classification)} {classification}.");
}
}
}
private static void GetUuidsToQuery(OVRAnchor anchor, HashSet uuidsToQuery)
{
if (anchor.TryGetComponent(out var container))
{
foreach (var uuid in container.Uuids)
{
uuidsToQuery.Add(uuid);
}
}
}
private static void CheckClassificationsInRooms(bool success, List rooms, IEnumerable requestedAnchorClassifications, OVRTask task)
{
if (!success)
{
Development.Log(nameof(OVRSceneManager),
$"{nameof(OVRAnchor.FetchAnchorsAsync)} failed on {nameof(DoesRoomSetupExist)}() request to fetch room anchors.");
return;
}
using (new OVRObjectPool.HashSetScope(out var uuidsToQuery))
using (new OVRObjectPool.ListScope(out var anchorUuids))
{
for (int i = 0; i < rooms.Count; i++)
{
GetUuidsToQuery(rooms[i], uuidsToQuery);
anchorUuids.AddRange(uuidsToQuery);
uuidsToQuery.Clear();
}
using (new OVRObjectPool.ListScope(out var roomAnchors))
{
OVRAnchor.FetchAnchorsAsync(anchorUuids, roomAnchors)
.ContinueWith(result => CheckIfAnchorsContainClassifications(result, roomAnchors, requestedAnchorClassifications, task));
}
}
}
private static void CheckIfAnchorsContainClassifications(bool success, List roomAnchors, IEnumerable requestedAnchorClassifications, OVRTask 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(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 anchors, List labels)
{
for (int i = 0; i < anchors.Count; i++)
{
var anchor = anchors[i];
if (anchor.TryGetComponent(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(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(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.Any())
{
roomLayout.Floor = OVRSceneRoom.SceneRoomsList[0].Floor;
roomLayout.Ceiling = OVRSceneRoom.SceneRoomsList[0].Ceiling;
roomLayout.Walls = OVRSceneRoom.SceneRoomsList[0]._walls;
}
return roomLayout;
}
private bool RequestSceneCapture(string requestString)
{
#if !UNITY_EDITOR
bool result = OVRPlugin.RequestSceneCapture(requestString, 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
}
private void OnEnable()
{
// Bind events
OVRManager.SceneCaptureComplete += OVRManager_SceneCaptureComplete;
if (OVRManager.display != null)
{
OVRManager.display.RecenteredPose += UpdateAllSceneAnchors;
}
if (!_cameraRig)
{
_cameraRig = FindObjectOfType();
}
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;
}
}
///
/// Determines if a point is inside of a 2d polygon.
///
/// The vertices that make up the bounds of the polygon
/// The target point to test
/// True if the point is inside the polygon, false otherwise
internal static bool PointInPolygon2D(NativeArray 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();
// 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
}