/* * 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 }