Files
2025-07-04 20:33:06 +03:00

350 lines
15 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 Unity.Collections;
using UnityEngine;
using static OVRPlugin;
/// <summary>
/// Represents an anchor.
/// </summary>
/// <remarks>
/// Scenes anchors are uniquely identified with their <see cref="Uuid"/>.
/// <para>You may dispose of an anchor by calling their <see cref="Dispose"/> method.</para>
/// </remarks>
public readonly struct OVRAnchor : IEquatable<OVRAnchor>, IDisposable
{
#region Static
public static readonly OVRAnchor Null = new OVRAnchor(0, Guid.Empty);
internal static OVRPlugin.SpaceQueryInfo GetQueryInfo(SpaceComponentType type,
OVRSpace.StorageLocation location, int maxResults, double timeout) => new OVRSpaceQuery.Options
{
QueryType = OVRPlugin.SpaceQueryType.Action,
ActionType = OVRPlugin.SpaceQueryActionType.Load,
ComponentFilter = type,
Location = location,
Timeout = timeout,
MaxResults = maxResults,
}.ToQueryInfo();
internal static OVRPlugin.SpaceQueryInfo GetQueryInfo(IEnumerable<Guid> uuids,
OVRSpace.StorageLocation location, double timeout) => new OVRSpaceQuery.Options
{
QueryType = OVRPlugin.SpaceQueryType.Action,
ActionType = OVRPlugin.SpaceQueryActionType.Load,
UuidFilter = uuids,
Location = location,
Timeout = timeout,
MaxResults = OVRSpaceQuery.Options.MaxUuidCount,
}.ToQueryInfo();
internal static OVRTask<bool> FetchAnchorsAsync(SpaceComponentType type, IList<OVRAnchor> anchors,
OVRSpace.StorageLocation location = OVRSpace.StorageLocation.Local,
int maxResults = OVRSpaceQuery.Options.MaxUuidCount, double timeout = 0.0)
=> FetchAnchors(anchors, GetQueryInfo(type, location, maxResults, timeout));
/// <summary>
/// Asynchronous method that fetches anchors with a specific component.
/// </summary>
/// <typeparam name="T">The type of component the fetched anchor must have.</typeparam>
/// <param name="anchors">IList that will get cleared and populated with the requested anchors.</param>s
/// <param name="location">Storage location to query</param>
/// <param name="maxResults">The maximum number of results the query can return</param>
/// <param name="timeout">Timeout in seconds for the query. Zero indicates the query does not timeout.</param>
/// <remarks>Dispose of the returned <see cref="OVRTask{T}"/> if you don't use the results</remarks>
/// <returns>An <see cref="OVRTask{T}"/> that will eventually let you test if the fetch was successful or not.
/// If the result is true, then the <see cref="anchors"/> parameter has been populated with the requested anchors.</returns>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="anchors"/> is `null`.</exception>
public static OVRTask<bool> FetchAnchorsAsync<T>(IList<OVRAnchor> anchors,
OVRSpace.StorageLocation location = OVRSpace.StorageLocation.Local,
int maxResults = OVRSpaceQuery.Options.MaxUuidCount, double timeout = 0.0)
where T : struct, IOVRAnchorComponent<T>
{
if (anchors == null)
{
throw new ArgumentNullException(nameof(anchors));
}
return FetchAnchorsAsync(default(T).Type, anchors, location, maxResults, timeout);
}
/// <summary>
/// Asynchronous method that fetches anchors with specifics uuids.
/// </summary>
/// <param name="uuids">Enumerable of uuids that anchors fetched must verify</param>
/// <param name="anchors">IList that will get cleared and populated with the requested anchors.</param>s
/// <param name="location">Storage location to query</param>
/// <param name="timeout">Timeout in seconds for the query. Zero indicates the query does not timeout.</param>
/// <remarks>Dispose of the returned <see cref="OVRTask{T}"/> if you don't use the results</remarks>
/// <returns>An <see cref="OVRTask{T}"/> that will eventually let you test if the fetch was successful or not.
/// If the result is true, then the <see cref="anchors"/> parameter has been populated with the requested anchors.</returns>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="uuids"/> is `null`.</exception>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="anchors"/> is `null`.</exception>
public static OVRTask<bool> FetchAnchorsAsync(IEnumerable<Guid> uuids, IList<OVRAnchor> anchors,
OVRSpace.StorageLocation location = OVRSpace.StorageLocation.Local, double timeout = 0.0)
{
if (uuids == null)
{
throw new ArgumentNullException(nameof(uuids));
}
if (anchors == null)
{
throw new ArgumentNullException(nameof(anchors));
}
return FetchAnchors(anchors, GetQueryInfo(uuids, location, timeout));
}
private static OVRTask<bool> FetchAnchors(IList<OVRAnchor> anchors, OVRPlugin.SpaceQueryInfo queryInfo)
{
if (anchors == null)
{
throw new ArgumentNullException(nameof(anchors));
}
anchors.Clear();
if (!OVRPlugin.QuerySpaces(queryInfo, out var requestId))
{
return OVRTask.FromResult(false);
}
var task = OVRTask.FromRequest<bool>(requestId);
task.SetInternalData(anchors);
return task;
}
internal static void OnSpaceQueryCompleteData(OVRDeserialize.SpaceQueryCompleteData data)
{
var requestId = data.RequestId;
var task = OVRTask.GetExisting<bool>(requestId);
if (!task.IsPending)
{
return;
}
if (!task.TryGetInternalData<IList<OVRAnchor>>(out var anchors) || anchors == null)
{
task.SetResult(false);
return;
}
if (!OVRPlugin.RetrieveSpaceQueryResults(requestId, out var rawResults, Allocator.Temp))
{
task.SetResult(false);
return;
}
foreach (var result in rawResults)
{
var anchor = new OVRAnchor(result.space, result.uuid);
anchors.Add(anchor);
}
rawResults.Dispose();
task.SetResult(true);
}
/// <summary>
/// Creates a new spatial anchor.
/// </summary>
/// <remarks>
/// Spatial anchor creation is asynchronous. This method initiates a request to create a spatial anchor at
/// <paramref name="trackingSpacePose"/>. The returned <see cref="OVRTask{TResult}"/> can be awaited or used to
/// track the completion of the request.
///
/// If spatial anchor creation fails, the resulting <see cref="OVRAnchor"/> will be <see cref="OVRAnchor.Null"/>.
/// </remarks>
/// <param name="trackingSpacePose">The pose, in tracking space, at which you wish to create the spatial anchor.</param>
/// <returns>A task which can be used to track completion of the request.</returns>
public static OVRTask<OVRAnchor> CreateSpatialAnchorAsync(Pose trackingSpacePose)
=> CreateSpatialAnchor(new SpatialAnchorCreateInfo
{
BaseTracking = GetTrackingOriginType(),
PoseInSpace = new Posef
{
Orientation = trackingSpacePose.rotation.ToFlippedZQuatf(),
Position = trackingSpacePose.position.ToFlippedZVector3f(),
},
Time = GetTimeInSeconds(),
}, out var requestId)
? OVRTask.FromRequest<OVRAnchor>(requestId)
: OVRTask.FromResult(Null);
/// <summary>
/// Creates a new spatial anchor.
/// </summary>
/// <remarks>
/// Spatial anchor creation is asynchronous. This method initiates a request to create a spatial anchor at
/// <paramref name="transform"/>. The returned <see cref="OVRTask{TResult}"/> can be awaited or used to
/// track the completion of the request.
///
/// If spatial anchor creation fails, the resulting <see cref="OVRAnchor"/> will be <see cref="OVRAnchor.Null"/>.
/// </remarks>
/// <param name="transform">The transform at which you wish to create the spatial anchor.</param>
/// <param name="centerEyeCamera">The `Camera` associated with the Meta Quest's center eye.</param>
/// <returns>A task which can be used to track completion of the request.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="transform"/> is `null`.</exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="centerEyeCamera"/> is `null`.</exception>
public static OVRTask<OVRAnchor> CreateSpatialAnchorAsync(Transform transform, Camera centerEyeCamera)
{
if (transform == null)
throw new ArgumentNullException(nameof(transform));
if (centerEyeCamera == null)
throw new ArgumentNullException(nameof(centerEyeCamera));
var pose = transform.ToTrackingSpacePose(centerEyeCamera);
return CreateSpatialAnchorAsync(new Pose
{
position = pose.position,
rotation = pose.orientation,
});
}
#endregion
internal ulong Handle { get; }
/// <summary>
/// Unique Identifier representing the anchor.
/// </summary>
public Guid Uuid { get; }
internal OVRAnchor(ulong handle, Guid uuid)
{
Handle = handle;
Uuid = uuid;
}
/// <summary>
/// Gets the anchor's component of a specific type.
/// </summary>
/// <typeparam name="T">The type of the component.</typeparam>
/// <returns>The requested component.</returns>
/// <remarks>Make sure the anchor supports the specified type of component using <see cref="SupportsComponent{T}"/></remarks>
/// <exception cref="InvalidOperationException">Thrown if the anchor doesn't support the specified type of component.</exception>
/// <seealso cref="TryGetComponent{T}"/>
/// <seealso cref="SupportsComponent{T}"/>
public T GetComponent<T>() where T : struct, IOVRAnchorComponent<T>
{
if (!TryGetComponent<T>(out var component))
{
throw new InvalidOperationException($"Anchor {Uuid} does not have component {typeof(T).Name}");
}
return component;
}
/// <summary>
/// Tries to get the anchor's component of a specific type.
/// </summary>
/// <param name="component">The requested component, as an <c>out</c> parameter.</param>
/// <typeparam name="T">The type of the component.</typeparam>
/// <returns>Whether or not the request succeeded. It may fail if the anchor doesn't support this type of component.</returns>
/// <seealso cref="GetComponent{T}"/>
public bool TryGetComponent<T>(out T component) where T : struct, IOVRAnchorComponent<T>
{
component = default(T);
var result = OVRPlugin.GetSpaceComponentStatusInternal(Handle, component.Type, out _, out _);
if (!result.IsSuccess())
{
return false;
}
component = component.FromAnchor(this);
return true;
}
/// <summary>
/// Tests whether or not the anchor supports a specific type of component.
/// </summary>
/// <remarks>
/// For performance reasons, we use xrGetSpaceComponentStatusFB, which can
/// result in an error in the logs when the component is not available.
///
/// This error does not have impact on the control flow. The alternative method,
/// <seealso cref="GetSupportedComponents(List{SpaceComponentType})"/> avoids
/// this error reporting, but does have performance constraints.
/// </remarks>
/// <typeparam name="T">The type of the component.</typeparam>
/// <returns>Whether or not the specified type of component is supported.</returns>
public bool SupportsComponent<T>() where T : struct, IOVRAnchorComponent<T>
{
var component = default(T);
var result = OVRPlugin.GetSpaceComponentStatusInternal(Handle, component.Type, out _, out _);
return result.IsSuccess();
}
/// <summary>
/// Get all the supported components of an anchor.
/// </summary>
/// <remarks>
/// For performance reasons, this method reuses data structures to
/// avoid allocations, and is therefore not considered thread safe.
///
/// Do not use in background threads, including async functions
/// started with <seealso cref="System.Threading.Tasks.Task.Run(Action)"/>
/// </remarks>
/// <param name="components">The list that will be cleared, then populated with
/// the supported components.</param>
/// <returns>Whether or not the request succeeded.</returns>
public bool GetSupportedComponents(List<SpaceComponentType> components)
{
components.Clear();
if (OVRPlugin.EnumerateSpaceSupportedComponents(Handle,
out var componentCount, SupportedComponentsArray))
{
for (var i = 0; i < componentCount; i++)
components.Add(SupportedComponentsArray[i]);
return true;
}
return false;
}
private static readonly SpaceComponentType[] SupportedComponentsArray = new SpaceComponentType[64];
public bool Equals(OVRAnchor other) => Handle.Equals(other.Handle) && Uuid.Equals(other.Uuid);
public override bool Equals(object obj) => obj is OVRAnchor other && Equals(other);
public static bool operator ==(OVRAnchor lhs, OVRAnchor rhs) => lhs.Equals(rhs);
public static bool operator !=(OVRAnchor lhs, OVRAnchor rhs) => !lhs.Equals(rhs);
public override int GetHashCode() => unchecked(Handle.GetHashCode() * 486187739 + Uuid.GetHashCode());
public override string ToString() => Uuid.ToString();
/// <summary>
/// Disposes of an anchor.
/// </summary>
/// <remarks>
/// Calling this method will destroy the anchor so that it won't be managed by internal systems until
/// the next time it is fetched again.
/// </remarks>
public void Dispose() => OVRPlugin.DestroySpace(Handle);
}