// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
// Copyright (c) (2021-2022) Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Software License Agreement, located here: https://www.magicleap.com/software-license-agreement-ml2
// Terms and conditions applicable to third-party materials accompanying this distribution may also be found in the top-level NOTICE file appearing herein.
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
// Disabling deprecated warning for the internal project
#pragma warning disable 618
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using UnityEngine.Scripting;
using UnityEngine.XR.ARSubsystems;
using UnityEngine.XR.MagicLeap.Native;
namespace UnityEngine.XR.MagicLeap
{
///
/// The Magic Leap implementation of the XRPlaneSubsystem. Do not create this directly.
/// Use PlanesSubsystemDescriptor.Create() instead.
///
[Preserve]
public sealed partial class PlanesSubsystem : XRPlaneSubsystem
{
public static partial class Extensions
{
public static Extensions.PlanesQuery Query
{
get => query;
set
{
QuerySet = true;
query = value;
}
}
internal static bool QuerySet
{
get;
private set;
}
private static Extensions.PlanesQuery query = default;
public struct PlanesQuery
{
///
/// The flags to apply to this query.
///
public PlanesSubsystem.Extensions.MLPlanesQueryFlags Flags;
///
/// The center of the bounding box which defines where planes extraction should occur.
///
public Vector3 BoundsCenter;
///
/// The rotation of the bounding box where planes extraction will occur.
///
public Quaternion BoundsRotation;
///
/// The size of the bounding box where planes extraction will occur.
///
public Vector3 BoundsExtents;
///
/// The maximum number of results that should be returned.
///
public uint MaxResults;
///
/// The minimum area (in squared meters) of planes to be returned. This value
/// cannot be lower than 0.04 (lower values will be capped to this minimum).
///
public float MinPlaneArea;
}
}
#if UNITY_2020_2_OR_NEWER
MagicLeapProvider magicLeapProvider => (MagicLeapProvider)provider;
#else
MagicLeapProvider magicLeapProvider;
protected override Provider CreateProvider()
{
magicLeapProvider = new MagicLeapProvider();
return magicLeapProvider;
}
#endif
internal const ulong planeTrackableIdSalt = 0xf52b75076e45ad88;
class MagicLeapProvider : Provider
{
internal Extensions.PlanesQuery defaultPlanesQuery
{
get
{
if (Extensions.QuerySet)
{
return Extensions.Query;
}
else
{
return new Extensions.PlanesQuery
{
Flags = _defaultQueryFlags,
BoundsCenter = Vector3.zero,
BoundsRotation = Quaternion.identity,
BoundsExtents = Vector3.one * 20f,
MaxResults = _maxResults,
MinPlaneArea = 0.25f
};
}
}
}
ulong _planesTracker = Native.MagicLeapNativeBindings.InvalidHandle;
ulong _QueryHandle = Native.MagicLeapNativeBindings.InvalidHandle;
uint _maxResults = 4;
uint _lastNumResults;
uint _previousLastNumResults = 0;
Extensions.MLPlanesQueryFlags _defaultQueryFlags = Extensions.MLPlanesQueryFlags.Polygons | Extensions.MLPlanesQueryFlags.Semantic_All;
Dictionary _planes = new Dictionary();
Extensions.MLPlaneBoundariesList _boundariesList;
Extensions.MLPlanesQueryFlags _requestedPlaneDetectionMode;
Extensions.MLPlanesQueryFlags _currentPlaneDetectionMode;
// todo: 2019-05-22: Unity.Collections.NativeHashMap would be better
// but introduces another package dependency. Probably not worth it
// for just this one thing, but if it becomes a dependency, we should
// switch to using the NativeHashMap (or NativeHashSet if it exists).
static HashSet currentSet = new HashSet();
public MagicLeapProvider() { }
public override PlaneDetectionMode requestedPlaneDetectionMode
{
get => _requestedPlaneDetectionMode.ToPlaneDetectionMode();
set
{
_requestedPlaneDetectionMode = value.ToMLQueryFlags();
_defaultQueryFlags = _requestedPlaneDetectionMode | Extensions.MLPlanesQueryFlags.Polygons | Extensions.MLPlanesQueryFlags.Semantic_All;
}
}
public override PlaneDetectionMode currentPlaneDetectionMode => _currentPlaneDetectionMode.ToPlaneDetectionMode();
private bool CreateClient()
{
if (_planesTracker != Native.MagicLeapNativeBindings.InvalidHandle)
{
// client already created
return true;
}
if (!MLPermissions.CheckPermission(MLPermission.SpatialMapping).IsOk)
{
// permission denied
return false;
}
var result = NativeBindings.MLPlanesCreate(out _planesTracker);
if (!MLResult.IsOK(result))
{
Debug.LogError($"Failed to start planes subsystem, reason: {result}");
return false;
}
if (_boundariesList.valid)
{
Debug.LogError($"Restarting the plane subsystem with an existing boundaries list.");
}
_boundariesList = Extensions.MLPlaneBoundariesList.Create();
return true;
}
public override void Start()
{
// Don't create client right away as permission request will not be approved yet
SubsystemFeatures.SetFeatureRequested(Feature.PlaneTracking, true);
}
public override void Stop()
{
if (_planesTracker != Native.MagicLeapNativeBindings.InvalidHandle)
{
if (_boundariesList.valid)
{
NativeBindings.MLPlanesReleaseBoundariesList(_planesTracker, ref _boundariesList);
_boundariesList = Extensions.MLPlaneBoundariesList.Create();
}
NativeBindings.MLPlanesDestroy(_planesTracker);
_planesTracker = Native.MagicLeapNativeBindings.InvalidHandle;
}
SubsystemFeatures.SetFeatureRequested(Feature.PlaneTracking, false);
_QueryHandle = Native.MagicLeapNativeBindings.InvalidHandle;
}
public override void Destroy() { }
public unsafe PlaneBoundaryCollection GetAllBoundariesForPlane(in TrackableId trackableId)
{
if (!_planes.TryGetValue(trackableId, out BoundedPlane plane))
return default;
// MLPlaneBoundaries is an array of boundaries, so planeBoundariesArray represents an array of MLPlaneBoundaries
// which is itself an array of boundaries.
var planeBoundariesArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(
_boundariesList.plane_boundaries,
(int)_boundariesList.plane_boundaries_count,
Allocator.None);
#if UNITY_EDITOR
var safetyHandle = AtomicSafetyHandle.Create();
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref planeBoundariesArray, safetyHandle);
#endif
// Find the plane boundaries with the given trackable id
foreach (var planeBoundaries in planeBoundariesArray)
{
var id = new TrackableId(planeBoundaries.id, planeTrackableIdSalt);
if (id == trackableId)
{
return new PlaneBoundaryCollection(planeBoundaries, plane.pose);
}
}
return default;
}
public unsafe override void GetBoundary(
TrackableId trackableId,
Allocator allocator,
ref NativeArray convexHullOut)
{
var boundaries = GetAllBoundariesForPlane(in trackableId);
if (boundaries.count > 0)
{
// TODO 2019-05-21: handle multiple boundaries?
using (var polygon = boundaries[0].GetPolygon(Allocator.TempJob))
{
ConvexHullGenerator.Giftwrap(polygon, allocator, ref convexHullOut);
return;
}
}
else
{
if (_planes.TryGetValue(trackableId, out BoundedPlane plane))
{
float halfHeight = plane.height * 0.5f;
float halfWidth = plane.width * 0.5f;
var calculatedBoundaries = new NativeArray(4, Allocator.Temp);
calculatedBoundaries[0] = new Vector2(halfWidth, halfHeight);
calculatedBoundaries[1] = new Vector2(-halfWidth, halfHeight);
calculatedBoundaries[2] = new Vector2(-halfWidth, -halfHeight);
calculatedBoundaries[3] = new Vector2(halfWidth, -halfHeight);
ConvexHullGenerator.Giftwrap(calculatedBoundaries, allocator, ref convexHullOut);
return;
}
}
CreateOrResizeNativeArrayIfNecessary(0, allocator, ref convexHullOut);
}
public unsafe override TrackableChanges GetChanges(BoundedPlane defaultPlane, Allocator allocator)
{
if (_planesTracker == Native.MagicLeapNativeBindings.InvalidHandle)
{
if (!CreateClient())
{
return default;
}
}
if (_QueryHandle == Native.MagicLeapNativeBindings.InvalidHandle)
{
_QueryHandle = BeginNewQuery();
return default;
}
else
{
// Get results
var mlPlanes = new NativeArray((int)_maxResults, Allocator.TempJob);
if (_boundariesList.valid)
{
NativeBindings.MLPlanesReleaseBoundariesList(_planesTracker, ref _boundariesList);
}
_boundariesList = Extensions.MLPlaneBoundariesList.Create();
try
{
var result = NativeBindings.MLPlanesQueryGetResultsWithBoundaries(
_planesTracker, _QueryHandle,
(Extensions.MLPlane*)mlPlanes.GetUnsafePtr(), out uint numResults, ref _boundariesList);
switch (result)
{
case MLResult.Code.Ok:
{
_previousLastNumResults = _lastNumResults;
_lastNumResults = numResults;
_QueryHandle = BeginNewQuery();
using (var uPlanes = new NativeArray((int)numResults, Allocator.TempJob))
{
// Generate unique tracker IDs based on number of planes with the same plane id (inner planes)
var planeIdCount = new Dictionary(); ;
var planeTrackableIds = new NativeArray(mlPlanes.Length, Allocator.TempJob);
for (int i = 0; i < mlPlanes.Length; ++i)
{
var planeId = mlPlanes[i].id;
if (!planeIdCount.TryGetValue(planeId, out ulong count))
{
count = 0;
}
var trackableId = new TrackableId(planeId, planeTrackableIdSalt + count);
planeIdCount[planeId] = ++count;
planeTrackableIds[i] = trackableId;
}
// Perform Unity plane conversion
new CopyPlaneResultsJob
{
planeTrackableIds = planeTrackableIds,
planesIn = mlPlanes,
planesOut = uPlanes
}.Schedule((int)numResults, 1).Complete();
planeTrackableIds.Dispose();
// Update plane states
var added = new NativeFixedList((int)numResults, Allocator.Temp);
var updated = new NativeFixedList((int)numResults, Allocator.Temp);
var removed = new NativeFixedList((int)_previousLastNumResults, Allocator.Temp);
currentSet.Clear();
for (int i = 0; i < numResults; ++i)
{
var uPlane = uPlanes[i];
var trackableId = uPlane.trackableId;
currentSet.Add(trackableId);
if (_planes.ContainsKey(trackableId))
{
updated.Add(uPlane);
}
else
{
added.Add(uPlane);
}
_planes[trackableId] = uPlane;
}
// Look for removed planes
foreach (var kvp in _planes)
{
var trackableId = kvp.Key;
if (!currentSet.Contains(trackableId))
{
removed.Add(trackableId);
}
}
foreach (var trackableId in removed)
{
_planes.Remove(trackableId);
}
using (added)
using (updated)
using (removed)
{
var changes = new TrackableChanges(
added.Length,
updated.Length,
removed.Length,
allocator);
added.CopyTo(changes.added);
updated.CopyTo(changes.updated);
removed.CopyTo(changes.removed);
return changes;
}
}
}
case MLResult.Code.Pending:
{
return default;
}
default:
{
_QueryHandle = BeginNewQuery();
return default;
}
}
}
finally
{
mlPlanes.Dispose();
}
}
}
ulong BeginNewQuery()
{
// We hit the max, so increase for next time
if (!Extensions.QuerySet && _maxResults == _lastNumResults)
{
_maxResults = _maxResults * 3 / 2;
}
var query = new Extensions.MLPlanesQuery(defaultPlanesQuery);
if (Extensions.QuerySet)
{
_maxResults = query.max_results;
}
ulong queryHandle;
var result = NativeBindings.MLPlanesQueryBegin(_planesTracker, in query, out queryHandle);
if (!MLResult.IsOK(result))
{
return Native.MagicLeapNativeBindings.InvalidHandle;
}
_currentPlaneDetectionMode = _requestedPlaneDetectionMode;
return queryHandle;
}
}
#if UNITY_XR_MAGICLEAP_PROVIDER
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
#endif
static void RegisterDescriptor()
{
XRPlaneSubsystemDescriptor.Create(new XRPlaneSubsystemDescriptor.Cinfo
{
id = MagicLeapXrProvider.PlanesSubsystemId,
providerType = typeof(MagicLeapProvider),
subsystemTypeOverride = typeof(PlanesSubsystem),
supportsVerticalPlaneDetection = true,
supportsArbitraryPlaneDetection = true,
supportsBoundaryVertices = true,
supportsClassification = true
});
}
internal class NativeBindings : MagicLeapNativeBindings
{
[DllImport(MLPerceptionClientDll, CallingConvention = CallingConvention.Cdecl)]
public static extern MLResult.Code MLPlanesCreate(out ulong planes_tracker);
[DllImport(MLPerceptionClientDll, CallingConvention = CallingConvention.Cdecl)]
public static extern MLResult.Code MLPlanesDestroy(ulong planes_tracker);
[DllImport(MLPerceptionClientDll, CallingConvention = CallingConvention.Cdecl)]
public static extern MLResult.Code MLPlanesQueryBegin(ulong planes_tracker, in Extensions.MLPlanesQuery query, out ulong request_handle);
[DllImport(MLPerceptionClientDll, CallingConvention = CallingConvention.Cdecl)]
public static extern unsafe MLResult.Code MLPlanesQueryGetResultsWithBoundaries(ulong planes_tracker, ulong planes_query, Extensions.MLPlane* out_results, out uint out_num_results, ref Extensions.MLPlaneBoundariesList out_boundaries);
[DllImport(MLPerceptionClientDll, CallingConvention = CallingConvention.Cdecl)]
public static extern MLResult.Code MLPlanesReleaseBoundariesList(ulong planes_tracker, ref Extensions.MLPlaneBoundariesList plane_boundaries);
}
}
}