/* * 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 UnityEngine; /// /// Represents a scene anchor. /// /// /// A scene anchor is a type of anchor that is provided by the system. It represents an item in the physical /// environment, such as a plane or volume. Scene anchors are created by the . /// /// /// /// [DisallowMultipleComponent] [HelpURL("https://developer.oculus.com/reference/unity/latest/class_o_v_r_scene_anchor")] public sealed class OVRSceneAnchor : MonoBehaviour { /// /// The runtime handle of this scene anchor. /// public OVRSpace Space { get; private set; } /// /// The universally unique identifier for this scene anchor. /// public Guid Uuid { get; private set; } /// /// Associated OVRAnchor /// public OVRAnchor Anchor { get; private set; } /// /// Indicates whether this anchor is tracked by the system. /// public bool IsTracked { get; internal set; } private static readonly Quaternion RotateY180 = Quaternion.Euler(0, 180, 0); private OVRPlugin.Posef? _pose = null; private bool _isLocatable = false; private readonly List _supportedComponents = new List(); private bool IsComponentSupported(OVRPlugin.SpaceComponentType spaceComponentType) { // late initialization and caching as this doesn't // change during the lifetime of a scene anchor if (_supportedComponents.Count == 0) { if (!Anchor.GetSupportedComponents(_supportedComponents)) return false; } return _supportedComponents.Contains(spaceComponentType); } internal bool IsComponentEnabled(OVRPlugin.SpaceComponentType spaceComponentType) => IsComponentSupported(spaceComponentType) && OVRPlugin.GetSpaceComponentStatus(Space, spaceComponentType, out var componentEnabled, out _) && componentEnabled; private void SyncComponent(OVRPlugin.SpaceComponentType spaceComponentType) where T : MonoBehaviour, IOVRSceneComponent { if (!IsComponentEnabled(spaceComponentType)) return; var component = GetComponent(); if (component) { // If the component already exists, then it means it was added before this component was valid, so we need // to initialize it. component.Initialize(); } else { gameObject.AddComponent(); } } internal void ClearPoseCache() { _pose = null; } public void Initialize(OVRAnchor anchor) { var space = (OVRSpace)anchor.Handle; var uuid = anchor.Uuid; if (Space.Valid) throw new InvalidOperationException($"[{uuid}] {nameof(OVRSceneAnchor)} has already been initialized."); if (!space.Valid) throw new ArgumentException($"[{uuid}] {nameof(space)} must be valid.", nameof(space)); Space = space; Uuid = uuid; Anchor = anchor; ClearPoseCache(); SceneAnchors[this.Uuid] = this; SceneAnchorsList.Add(this); AnchorReferenceCountDictionary.TryGetValue(Space, out var referenceCount); AnchorReferenceCountDictionary[Space] = referenceCount + 1; // certain components are not locatable, such as room. _isLocatable = IsComponentSupported(OVRPlugin.SpaceComponentType.Locatable); // Generally, we want to set the transform as soon as possible, but there is a valid use case where we want to // disable this component as soon as its added to override the transform. if (enabled) { if (!_isLocatable) { OVRSceneManager.Development.Log(nameof(OVRSceneAnchor), $"[{uuid}] Skiping transform set, as the entity is not locatable.", gameObject); } else if (TryUpdateTransform(false)) { IsTracked = true; OVRSceneManager.Development.Log(nameof(OVRSceneAnchor), $"[{uuid}] Initial transform set.", gameObject); } else { OVRSceneManager.Development.LogWarning(nameof(OVRSceneAnchor), $"[{uuid}] {nameof(OVRPlugin.TryLocateSpace)} failed. The entity may have the wrong initial transform.", gameObject); } } SyncComponent(OVRPlugin.SpaceComponentType.SemanticLabels); SyncComponent(OVRPlugin.SpaceComponentType.Bounded3D); SyncComponent(OVRPlugin.SpaceComponentType.Bounded2D); } /// /// Initializes this scene anchor from an existing scene anchor. /// /// An existing from which to initialize this scene anchor. /// Thrown if is `null`. /// Thrown if this is already associated with a scene anchor. public void InitializeFrom(OVRSceneAnchor other) { if (other == null) throw new ArgumentNullException(nameof(other)); Initialize(other.Anchor); } /// /// Get the list of all scene anchors. /// /// A list of to populate. /// Thrown if is `null`. public static void GetSceneAnchors(List anchors) { if (anchors == null) throw new ArgumentNullException(nameof(anchors)); anchors.Clear(); anchors.AddRange(SceneAnchorsList); } internal bool TryUpdateTransform(bool useCache) { if (!Space.Valid || !enabled || !_isLocatable) return false; if (!useCache || _pose == null) { var tryLocateSpace = OVRPlugin.TryLocateSpace(Space, OVRPlugin.GetTrackingOriginType(), out var pose, out var locationFlags); if (!tryLocateSpace || !locationFlags.IsOrientationValid() || !locationFlags.IsPositionValid()) { return false; } _pose = pose; } // NOTE: This transformation performs the following steps: // 1. Flip Z to convert from OpenXR's right-handed to Unity's left-handed coordinate system. // OpenXR Unity // | y y | / z // | | / // +----> x +----> x // / // z/ (normal) // // 2. (1) means that Z now points in the opposite direction from OpenXR. However, the design is such that a // plane's normal should coincide with +Z, so we rotate 180 degrees around the +Y axis to make Z now point // in the intended direction. // OpenXR Unity // | y y | // | | // +----> x <----+ // / / // z/ z / (normal) // // 3. Convert from tracking space to world space. var worldSpacePose = new OVRPose { position = _pose.Value.Position.FromFlippedZVector3f(), orientation = _pose.Value.Orientation.FromFlippedZQuatf() * RotateY180 }.ToWorldSpacePose(Camera.main); transform.SetPositionAndRotation(worldSpacePose.position, worldSpacePose.orientation); return true; } private void OnDestroy() { SceneAnchors.Remove(this.Uuid); SceneAnchorsList.Remove(this); if (!Space.Valid) { return; } if (!AnchorReferenceCountDictionary.TryGetValue(Space, out var referenceCount)) { OVRSceneManager.Development.LogError(nameof(OVRSceneAnchor), $"[Anchor {Space.Handle}] has not been found, can't find it for deletion", gameObject); return; } if (referenceCount == 1) { // last reference to this anchor, delete it if (Space.Valid) { OVRPlugin.DestroySpace(Space); } // remove instead of decrement to not waste memory AnchorReferenceCountDictionary.Remove(Space); } else { AnchorReferenceCountDictionary[Space] = referenceCount - 1; } } private static readonly Dictionary AnchorReferenceCountDictionary = new Dictionary(); internal static readonly Dictionary SceneAnchors = new Dictionary(); internal static readonly List SceneAnchorsList = new List(); } internal interface IOVRSceneComponent { void Initialize(); }