1
0
mirror of https://github.com/xiaopeng12138/MaiDXR.git synced 2024-12-18 20:05:52 +01:00
MaiDXR/Assets/Oculus/VR/Scripts/OVRSpatialAnchor.cs
2022-08-20 21:35:57 +02:00

386 lines
14 KiB
C#

/*
* 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.Diagnostics;
using Unity.Collections;
using UnityEngine;
using Debug = UnityEngine.Debug;
/// <summary>
/// Represents a spatial anchor.
/// </summary>
/// <remarks>
/// This component can be used in two ways: to create a new spatial anchor or to associate with an existing spatial
/// anchor.
///
/// To create a new spatial anchor, simply add this component to any GameObject. The transform of the GameObject is used
/// to create a new spatial anchor in the Oculus Runtime. Afterwards, the GameObject's transform will be updated
/// automatically. The creation operation is asynchronous, and, if it fails, this component will be destroyed.
///
/// To associate an existing spatial anchor with this component, you will need the spatial anchor's space handle and its
/// uuid, which can be obtained using <see cref="OVRPlugin.QuerySpaces"/>. After adding this component to a GameObject,
/// call <see cref="InitializeFromExisting"/> before the next frame, i.e., before `Start` is called on this component.
/// </remarks>
[DisallowMultipleComponent]
public class OVRSpatialAnchor : MonoBehaviour
{
private bool _startCalled;
private ulong _requestId;
/// <summary>
/// The space associated with this spatial anchor.
/// </summary>
/// <remarks>
/// The <see cref="OVRSpace"/> represents the runtime instance of the spatial anchor and will change across
/// different sessions.
/// </remarks>
public OVRSpace Space { get; private set; }
/// <summary>
/// The UUID associated with this spatial anchor.
/// </summary>
/// <remarks>
/// UUIDs persist across sessions and applications. If you load a persisted anchor, you can use the UUID to identify
/// it.
/// </remarks>
public Guid Uuid { get; private set; }
/// <summary>
/// Whether the spatial anchor has been created.
/// </summary>
/// <remarks>
/// Creation is asynchronous and may take several frames. If creation fails, this component is destroyed.
/// </remarks>
public bool Created => Space.Valid;
/// <summary>
/// Initializes this component from an existing space handle and uuid, e.g., the result of a call to
/// <see cref="OVRPlugin.QuerySpaces"/>.
/// </summary>
/// <remarks>
/// This method allows you to associate this component with an existing spatial anchor, e.g., one that was saved in
/// a previous session. Do not call this method to create a new spatial anchor.
///
/// If you call this method, you must do so prior to the component's `Start` method. You cannot change the spatial
/// anchor associated with this component after that.
/// </remarks>
/// <param name="space">The existing <see cref="OVRSpace"/> to associate with this spatial anchor.</param>
/// <param name="uuid">The universally unique identifier to associate with this spatial anchor.</param>
/// <exception cref="InvalidOperationException">Thrown if `Start` has already been called on this component.</exception>
/// <exception cref="ArgumentException">Thrown if <paramref name="space"/> is not <see cref="OVRSpace.Valid"/>.</exception>
public void InitializeFromExisting(OVRSpace space, Guid uuid)
{
if (_startCalled)
throw new InvalidOperationException($"Cannot call {nameof(InitializeFromExisting)} after {nameof(Start)}. This must be set once upon creation.");
if (!space.Valid)
{
Destroy(this);
throw new ArgumentException($"Invalid space {space}.", nameof(space));
}
InitializeUnchecked(space, uuid);
}
/// <summary>
/// Saves the <see cref="OVRSpatialAnchor"/> to local persistent storage.
/// </summary>
/// <remarks>
/// This method is asynchronous; use <paramref name="onComplete"/> to be notified of completion.
///
/// When saved, an <see cref="OVRSpatialAnchor"/> can be loaded by a different session or application. Use the
/// <see cref="Uuid"/> to identify the same <see cref="OVRSpatialAnchor"/> at a future time.
/// </remarks>
/// <param name="onComplete">
/// Invoked when the save operation completes. May be null. Parameters are
/// - <see cref="OVRSpatialAnchor"/>: The anchor being saved.
/// - `bool`: A value indicating whether the save operation succeeded.
/// </param>
public void Save(Action<OVRSpatialAnchor, bool> onComplete = null)
{
if (OVRPlugin.SaveSpace(Space, OVRPlugin.SpaceStorageLocation.Local,
OVRPlugin.SpaceStoragePersistenceMode.Indefinite, out var requestId))
{
Development.LogRequest(requestId, $"[{Uuid}] Saving spatial anchor...");
if (onComplete != null)
{
_completionDelegates[requestId] = new AnchorDelegatePair
{
Anchor = this,
Delegate = onComplete
};
}
}
else
{
Development.LogError($"[{Uuid}] {nameof(OVRPlugin)}.{nameof(OVRPlugin.SaveSpace)} failed.");
onComplete?.Invoke(this, false);
}
}
/// <summary>
/// Erases the <see cref="OVRSpatialAnchor"/> from persistent storage.
/// </summary>
/// <remarks>
/// This method is asynchronous; use <paramref name="onComplete"/> to be notified of completion.
/// </remarks>
/// <param name="onComplete">
/// Invoked when the erase operation completes. May be null. Parameters are
/// - <see cref="OVRSpatialAnchor"/>: The anchor being erased.
/// - `bool`: A value indicating whether the erase operation succeeded.
/// </param>
public void Erase(Action<OVRSpatialAnchor, bool> onComplete = null)
{
if (OVRPlugin.EraseSpace(Space, OVRPlugin.SpaceStorageLocation.Local, out var requestId))
{
Development.LogRequest(requestId, $"[{Uuid}] Erasing spatial anchor...");
if (onComplete != null)
{
_completionDelegates[requestId] = new AnchorDelegatePair
{
Anchor = this,
Delegate = onComplete
};
}
}
else
{
Development.LogError($"[{Uuid}] {nameof(OVRPlugin)}.{nameof(OVRPlugin.EraseSpace)} failed.");
onComplete?.Invoke(this, false);
}
}
// Initializes this component without checking preconditions
private void InitializeUnchecked(OVRSpace space, Guid uuid)
{
Space = space;
Uuid = uuid;
OVRPlugin.SetSpaceComponentStatus(Space, OVRPlugin.SpaceComponentType.Locatable, true, 0, out _);
OVRPlugin.SetSpaceComponentStatus(Space, OVRPlugin.SpaceComponentType.Storable, true, 0, out _);
// Try to update the pose as soon as we can.
UpdateTransform();
}
private void Start()
{
_startCalled = true;
if (Space.Valid)
{
Development.Log($"[{Space}] Created spatial anchor from existing space.");
}
else
{
CreateSpatialAnchor();
}
}
private void Update()
{
if (Space.Valid)
{
UpdateTransform();
}
}
private void OnDestroy()
{
if (Space.Valid)
{
OVRPlugin.DestroySpace(Space);
}
}
private OVRPose GetTrackingSpacePose()
{
var mainCamera = Camera.main;
if (mainCamera)
{
return transform.ToTrackingSpacePose(mainCamera);
}
Development.LogWarning($"No main camera found. Using world-space pose.");
return transform.ToOVRPose(isLocal: false);
}
private void CreateSpatialAnchor()
{
if (OVRPlugin.CreateSpatialAnchor(new OVRPlugin.SpatialAnchorCreateInfo
{
BaseTracking = OVRPlugin.GetTrackingOriginType(),
PoseInSpace = GetTrackingSpacePose().ToPosef(),
Time = OVRPlugin.GetTimeInSeconds(),
}, out _requestId))
{
Development.LogRequest(_requestId, $"Creating spatial anchor...");
_creationRequests[_requestId] = this;
}
else
{
Destroy(this);
Development.LogError($"{nameof(OVRPlugin)}.{nameof(OVRPlugin.CreateSpatialAnchor)} failed. Destroying {nameof(OVRSpatialAnchor)} component.");
}
}
private void UpdateTransform()
{
if (!OVRPlugin.TryLocateSpace(Space, OVRPlugin.GetTrackingOriginType(), out var posef)) return;
var pose = posef.ToOVRPose();
var mainCamera = Camera.main;
if (mainCamera)
{
pose = pose.ToWorldSpacePose(mainCamera);
}
transform.SetPositionAndRotation(pose.position, pose.orientation);
}
private struct AnchorDelegatePair
{
public OVRSpatialAnchor Anchor;
public Action<OVRSpatialAnchor, bool> Delegate;
}
private static readonly Dictionary<ulong, OVRSpatialAnchor> _creationRequests =
new Dictionary<ulong, OVRSpatialAnchor>();
private static readonly Dictionary<ulong, AnchorDelegatePair> _completionDelegates =
new Dictionary<ulong, AnchorDelegatePair>();
static OVRSpatialAnchor()
{
OVRManager.SpatialAnchorCreateComplete += OnSpatialAnchorCreateComplete;
OVRManager.SpaceSaveComplete += OnSpaceSaveComplete;
OVRManager.SpaceEraseComplete += OnSpaceEraseComplete;
}
private static void InvokeDelegate(ulong requestId, bool result)
{
if (_completionDelegates.TryGetValue(requestId, out var value))
{
_completionDelegates.Remove(requestId);
value.Delegate(value.Anchor, result);
}
}
private static void OnSpatialAnchorCreateComplete(ulong requestId, bool success, OVRSpace space, Guid uuid)
{
Development.LogRequestResult(requestId, success,
$"[{uuid}] Spatial anchor created.",
$"Failed to create spatial anchor. Destroying {nameof(OVRSpatialAnchor)} component.");
if (!_creationRequests.TryGetValue(requestId, out var anchor)) return;
_creationRequests.Remove(requestId);
if (success && anchor)
{
// All good; complete setup of OVRSpatialAnchor component.
anchor.InitializeUnchecked(space, uuid);
}
else if (success && !anchor)
{
// Creation succeeded, but the OVRSpatialAnchor component was destroyed before the callback completed.
OVRPlugin.DestroySpace(space);
}
else if (!success && anchor)
{
// The OVRSpatialAnchor component exists but creation failed.
Destroy(anchor);
}
// else if creation failed and the OVRSpatialAnchor component was destroyed, nothing to do.
}
private static void OnSpaceSaveComplete(ulong requestId, OVRSpace space, bool result, Guid uuid)
{
Development.LogRequestResult(requestId, result,
$"[{uuid}] Saved.",
$"[{uuid}] Save failed.");
InvokeDelegate(requestId, result);
}
private static void OnSpaceEraseComplete(ulong requestId, bool result, Guid uuid, OVRPlugin.SpaceStorageLocation location)
{
Development.LogRequestResult(requestId, result,
$"[{uuid}] Erased.",
$"[{uuid}] Erase failed.");
InvokeDelegate(requestId, result);
}
private static class Development
{
[Conditional("DEVELOPMENT_BUILD")]
public static void Log(string message) => Debug.Log($"[{nameof(OVRSpatialAnchor)}] {message}");
[Conditional("DEVELOPMENT_BUILD")]
public static void LogWarning(string message) => Debug.LogWarning($"[{nameof(OVRSpatialAnchor)}] {message}");
[Conditional("DEVELOPMENT_BUILD")]
public static void LogError(string message) => Debug.LogError($"[{nameof(OVRSpatialAnchor)}] {message}");
#if DEVELOPMENT_BUILD
private static readonly HashSet<ulong> _requests = new HashSet<ulong>();
#endif // DEVELOPMENT_BUILD
[Conditional("DEVELOPMENT_BUILD")]
public static void LogRequest(ulong requestId, string message)
{
#if DEVELOPMENT_BUILD
_requests.Add(requestId);
#endif // DEVELOPMENT_BUILD
Log($"({requestId}) {message}");
}
[Conditional("DEVELOPMENT_BUILD")]
public static void LogRequestResult(ulong requestId, bool result, string successMessage, string failureMessage)
{
#if DEVELOPMENT_BUILD
// Not a request we're tracking
if (!_requests.Remove(requestId)) return;
#endif // DEVELOPMENT_BUILD
if (result)
{
Log($"({requestId}) {successMessage}");
}
else
{
LogError($"({requestId}) {failureMessage}");
}
}
}
}