/* * 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.Linq; using UnityEngine; /// /// Represents a type Scene anchor. /// [DisallowMultipleComponent] [RequireComponent(typeof(OVRSceneAnchor))] [HelpURL("https://developer.oculus.com/reference/unity/latest/class_o_v_r_scene_room")] public class OVRSceneRoom : MonoBehaviour, IOVRSceneComponent { /// /// The representing the floor of the room. /// public OVRScenePlane Floor { get; private set; } /// /// The representing the ceiling of the room. /// public OVRScenePlane Ceiling { get; private set; } /// /// The set of representing the walls of the room. /// public OVRScenePlane[] Walls { get; private set; } = Array.Empty(); internal List _walls = new List(); private readonly Dictionary _orderedRoomGuids = new Dictionary(); private Comparison _wallOrderComparer; private OVRSceneAnchor _sceneAnchor; private OVRSceneManager _sceneManager; internal HashSet _uuidToQuery = new HashSet(); private List _roomAnchors = OVRObjectPool.Get>(); internal static readonly Dictionary SceneRooms = new Dictionary(); internal static readonly List SceneRoomsList = new List(); private int _taskCount; private Action _onFetchAnchorsCompleted; private Action _onAnchorLocalizationCompleted; private void Awake() { _sceneAnchor = GetComponent(); _sceneManager = FindObjectOfType(); _wallOrderComparer = (planeA, planeB) => { bool TryGetUuid(OVRScenePlane plane, out int index) { var guid = plane.GetComponent().Uuid; if (_orderedRoomGuids.TryGetValue(guid, out index)) return true; OVRSceneManager.Development.LogWarning(nameof(OVRSceneRoom), $"{nameof(OVRScenePlane)} {guid} does not belong to the current room layout."); return false; } if (!TryGetUuid(planeA, out var indexA)) return 0; if (!TryGetUuid(planeB, out var indexB)) return 0; return indexA.CompareTo(indexB); }; if (_sceneAnchor.Space.Valid) { ((IOVRSceneComponent)this).Initialize(); } _onFetchAnchorsCompleted = OnFetchAnchorsCompleted; _onAnchorLocalizationCompleted = OnLocalizationCompleted; } void IOVRSceneComponent.Initialize() { GetUuidsToQuery(); SceneRooms[_sceneAnchor.Uuid] = this; SceneRoomsList.Add(this); } internal void LoadRoom() { if (!_uuidToQuery.Any()) return; _roomAnchors.Clear(); OVRAnchor.FetchAnchorsAsync(_uuidToQuery, _roomAnchors).ContinueWith(_onFetchAnchorsCompleted); } private void OnFetchAnchorsCompleted(bool success) { if (!success) { _sceneManager.Verbose?.LogWarning(nameof(OVRSceneRoom), "Failed to fetch the room anchors."); return; } _walls.Clear(); _taskCount = _roomAnchors.Count; foreach (var anchor in _roomAnchors) { // Check if already exist. if (OVRSceneAnchor.SceneAnchors.ContainsKey(anchor.Uuid)) { OVRSceneAnchor.SceneAnchors[anchor.Uuid].IsTracked = true; continue; } if (!anchor.TryGetComponent(out OVRLocatable locatableComponent)) { continue; } if (locatableComponent.IsEnabled) { OnLocalizationCompleted(true, anchor); continue; } locatableComponent.SetEnabledAsync(true).ContinueWith(_onAnchorLocalizationCompleted, anchor); } } private void OnLocalizationCompleted(bool success, OVRAnchor anchor) { _taskCount--; if (!success) { _sceneManager.Verbose?.LogWarning(nameof(OVRSceneRoom), $"Failed to enable the {nameof(OVRLocatable)} component for anchor {anchor.Uuid}", gameObject); return; } OVRPlugin.GetSpaceComponentStatus(anchor.Handle, OVRPlugin.SpaceComponentType.Bounded2D, out bool bounded2dEnabled, out _); OVRPlugin.GetSpaceComponentStatus(anchor.Handle, OVRPlugin.SpaceComponentType.Bounded3D, out bool bounded3dEnabled, out _); var isStrictly2D = bounded2dEnabled && !bounded3dEnabled; OVRPlugin.GetSpaceComponentStatus(anchor.Handle, OVRPlugin.SpaceComponentType.TriangleMesh, out bool triangleMeshEnabled, out _); isStrictly2D = bounded2dEnabled && !(bounded3dEnabled || triangleMeshEnabled); // The plane prefab is for anchors that are only 2D, i.e. they only have // a 2D component. If a volume component exists, we use a volume prefab, // else we pass null (prefab overrides may be used) var prefab = isStrictly2D ? _sceneManager.PlanePrefab : (bounded3dEnabled ? _sceneManager.VolumePrefab : null); var sceneAnchor = _sceneManager.InstantiateSceneAnchor(anchor, prefab); if (sceneAnchor != null) { sceneAnchor.transform.parent = transform; if (isStrictly2D) UpdateRoomInformation(sceneAnchor.GetComponent()); } if (_taskCount == 0) { _walls.Sort(_wallOrderComparer); Walls = _walls.ToArray(); _sceneManager.Verbose?.Log(nameof(OVRSceneRoom), "Scene room loaded successfully.", gameObject); // Room load completed. _sceneManager.OnSceneRoomLoadCompleted(); } } private void UpdateRoomInformation(OVRScenePlane plane) { if (!plane.TryGetComponent(out OVRSemanticClassification classification)) return; foreach (var label in classification.Labels) { switch (label) { case OVRSceneManager.Classification.Floor: Floor = plane; break; case OVRSceneManager.Classification.Ceiling: Ceiling = plane; break; case OVRSceneManager.Classification.WallFace: _walls.Add(plane); break; } } } private void GetUuidsToQuery() { _uuidToQuery.Clear(); if (!_sceneAnchor.Anchor.TryGetComponent(out var container)) { return; } foreach (var uuid in container.Uuids) { _uuidToQuery.Add(uuid); } _sceneManager.Verbose?.Log(nameof(OVRSceneRoom), $"{nameof(_sceneAnchor.Anchor.TryGetComponent)}<{nameof(OVRAnchorContainer)}>: success [{true}], count [{_uuidToQuery.Count}]"); if (!_sceneAnchor.Anchor.TryGetComponent(out var roomLayout)) { _sceneManager.Verbose?.LogWarning(nameof(OVRSceneRoom), $"[{_sceneAnchor.Uuid}] has component {nameof(OVRPlugin.SpaceComponentType.RoomLayout)} " + $"but {nameof(_sceneAnchor.Anchor.TryGetComponent)}<{nameof(OVRRoomLayout)}> failed. Ignoring room.", gameObject); } if (!roomLayout.TryGetRoomLayout(out var ceilingUuid, out var floorUuid, out var wallUuids)) { _sceneManager.Verbose?.LogWarning(nameof(OVRSceneRoom), $"Failed to get the ceiling, floor and walls unique identifiers.", gameObject); return; } // save room ids and add to queryables (duplicates are filtered) if (!ceilingUuid.Equals(Guid.Empty)) _uuidToQuery.Add(ceilingUuid); if (!floorUuid.Equals(Guid.Empty)) _uuidToQuery.Add(floorUuid); _orderedRoomGuids.Clear(); int validWallsCount = 0; foreach (var wallUuid in wallUuids) { _sceneManager.Verbose?.Log(nameof(OVRSceneManager), $"{nameof(roomLayout.TryGetRoomLayout)}: wall [{wallUuid}]", gameObject); _orderedRoomGuids[wallUuid] = validWallsCount++; if (!wallUuid.Equals(Guid.Empty)) _uuidToQuery.Add(wallUuid); } } private void OnDestroy() { SceneRooms.Remove(_sceneAnchor.Uuid); SceneRoomsList.Remove(this); } }