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)); } } } }