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