using System;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.XR.ARSubsystems;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityEngine.XR.ARFoundation
{
///
/// Represents a plane (that is, a flat surface) detected by an AR device.
///
///
/// Generated by the when an AR device detects
/// a plane in the environment.
///
/// Related information: AR Plane component
///
[DefaultExecutionOrder(ARUpdateOrder.k_Plane)]
[DisallowMultipleComponent]
[HelpURL("features/plane-detection/arplane")]
public sealed class ARPlane : ARTrackable
{
[SerializeField]
[Tooltip("The largest value by which a plane's vertex position may change before the boundaryChanged event is invoked. Units are in meters.")]
float m_VertexChangedThreshold = 0.01f;
NativeArray m_Boundary;
NativeArray m_OldBoundary;
bool m_HasBoundaryChanged;
///
/// The largest value by which a plane's vertex could change before the mesh is regenerated. Units are in meters.
///
public float vertexChangedThreshold
{
get => m_VertexChangedThreshold;
set => m_VertexChangedThreshold = Mathf.Max(0f, value);
}
///
/// Invoked when any vertex in the plane's boundary changes by more than .
///
public event Action boundaryChanged;
///
/// Gets the normal to this plane in world space.
///
public Vector3 normal => transform.up;
///
/// The which has subsumed this plane, or null
/// if this plane has not been subsumed.
///
public ARPlane subsumedBy { get; internal set; }
///
/// The alignment of this plane.
///
public PlaneAlignment alignment => sessionRelativeData.alignment;
///
/// The classification of this plane.
///
[Obsolete("classification has been deprecated in AR Foundation 6.0. Use classifications instead.")]
public PlaneClassification classification
{
get
{
PlaneClassification classificationTemp = PlaneClassification.None;
classificationTemp.ConvertFromPlaneClassifications(sessionRelativeData.classifications);
return classificationTemp;
}
}
///
/// The classifications of this plane.
///
public PlaneClassifications classifications => sessionRelativeData.classifications;
///
/// The 2D center point, in plane space
///
public Vector2 centerInPlaneSpace => sessionRelativeData.center;
///
/// The 3D center point, in Unity world space.
///
public Vector3 center => transform.TransformPoint(new Vector3(centerInPlaneSpace.x, 0, centerInPlaneSpace.y));
///
/// The physical extents (half dimensions) of the plane in meters.
///
public Vector2 extents => sessionRelativeData.extents;
///
/// The physical size (dimensions) of the plane in meters.
///
public Vector2 size => sessionRelativeData.size;
///
/// Get the infinite plane associated with this .
///
public Plane infinitePlane => new Plane(normal, transform.position);
///
/// The plane's boundary points, in plane space, that is, relative to this 's
/// local position and rotation.
///
public unsafe NativeArray boundary
{
get
{
if (!m_Boundary.IsCreated)
return default(NativeArray);
var boundary = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(
m_Boundary.GetUnsafePtr(),
m_Boundary.Length,
Allocator.None);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(
ref boundary,
NativeArrayUnsafeUtility.GetAtomicSafetyHandle(m_Boundary));
#endif
return boundary;
}
}
internal void UpdateBoundary(XRPlaneSubsystem subsystem)
{
// subsystem cannot be null here
if (subsystem.subsystemDescriptor.supportsBoundaryVertices)
{
subsystem.GetBoundary(trackableId, Allocator.Persistent, ref m_Boundary);
}
else
{
if (!m_Boundary.IsCreated)
{
m_Boundary = new NativeArray(4, Allocator.Persistent);
}
else if (m_Boundary.Length != 4)
{
m_Boundary.Dispose();
m_Boundary = new NativeArray(4, Allocator.Persistent);
}
var extents = sessionRelativeData.extents;
m_Boundary[0] = new Vector2(-extents.x, -extents.y);
m_Boundary[1] = new Vector2(-extents.x, extents.y);
m_Boundary[2] = new Vector2(extents.x, extents.y);
m_Boundary[3] = new Vector2(extents.x, -extents.y);
}
if (boundaryChanged != null)
CheckForBoundaryChanges();
}
void OnValidate()
{
vertexChangedThreshold = Mathf.Max(0f, vertexChangedThreshold);
}
#if UNITY_EDITOR
void Awake()
{
AssemblyReloadEvents.beforeAssemblyReload += DisposeNativeContainers;
}
#endif
void OnDestroy()
{
DisposeNativeContainers();
#if UNITY_EDITOR
AssemblyReloadEvents.beforeAssemblyReload -= DisposeNativeContainers;
#endif
}
void DisposeNativeContainers()
{
if (m_OldBoundary.IsCreated)
m_OldBoundary.Dispose();
if (m_Boundary.IsCreated)
m_Boundary.Dispose();
}
void CheckForBoundaryChanges()
{
if (m_Boundary.Length != m_OldBoundary.Length)
{
CopyBoundaryAndSetChangedFlag();
}
else if (vertexChangedThreshold == 0f)
{
// Don't need to check each vertex because it will always
// be "different" if threshold is zero.
CopyBoundaryAndSetChangedFlag();
}
else
{
// Counts are the same; check each vertex
var thresholdSquared = vertexChangedThreshold * vertexChangedThreshold;
for (int i = 0; i < m_Boundary.Length; ++i)
{
var diffSquared = (m_Boundary[i] - m_OldBoundary[i]).sqrMagnitude;
if (diffSquared > thresholdSquared)
{
CopyBoundaryAndSetChangedFlag();
break;
}
}
}
}
void CopyBoundaryAndSetChangedFlag()
{
// Copy new boundary
if (m_OldBoundary.IsCreated)
{
// If the lengths are different, then we need
// to reallocate, but otherwise, we can reuse
if (m_OldBoundary.Length != m_Boundary.Length)
{
m_OldBoundary.Dispose();
m_OldBoundary = new NativeArray(m_Boundary.Length, Allocator.Persistent);
}
}
else
{
m_OldBoundary = new NativeArray(m_Boundary.Length, Allocator.Persistent);
}
m_OldBoundary.CopyFrom(m_Boundary);
m_HasBoundaryChanged = true;
}
void Update()
{
if (m_HasBoundaryChanged && boundaryChanged != null)
{
m_HasBoundaryChanged = false;
boundaryChanged?.Invoke(new ARPlaneBoundaryChangedEventArgs(this));
}
}
}
}