|
|
using System;using System.Collections.Generic;using Unity.Collections;using UnityEngine.Serialization;using UnityEngine.XR.ARSubsystems;
namespace UnityEngine.XR.ARFoundation{ /// <summary>
/// A manager for <see cref="ARPlane"/>s. Creates, updates, and removes
/// <c>GameObject</c>s in response to detected surfaces in the physical
/// environment.
/// </summary>
[DefaultExecutionOrder(ARUpdateOrder.k_PlaneManager)] [DisallowMultipleComponent] [RequireComponent(typeof(ARSessionOrigin))] [HelpURL("https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@3.0/api/UnityEngine.XR.ARFoundation.ARPlaneManager.html")] public sealed class ARPlaneManager : ARTrackableManager< XRPlaneSubsystem, XRPlaneSubsystemDescriptor, BoundedPlane, ARPlane>, IRaycaster { [SerializeField] [Tooltip("If not null, instantiates this prefab for each created plane.")] GameObject m_PlanePrefab;
/// <summary>
/// Getter/setter for the Plane Prefab.
/// </summary>
public GameObject planePrefab { get { return m_PlanePrefab; } set { m_PlanePrefab = value; } }
[SerializeField, PlaneDetectionModeMask] [Tooltip("The types of planes to detect.")] [FormerlySerializedAs("PlaneDetectionFlags")] PlaneDetectionMode m_DetectionMode = k_PlaneDetectionModeEverything;
/// <summary>
/// Get or set the <c>PlaneDetectionMode</c> to use for plane detection.
/// </summary>
public PlaneDetectionMode detectionMode { get { if (m_DetectionMode == k_PlaneDetectionModeEverything) return PlaneDetectionMode.Horizontal | PlaneDetectionMode.Vertical;
return m_DetectionMode; } set { m_DetectionMode = value;
if (subsystem != null) subsystem.planeDetectionMode = detectionMode; } }
/// <summary>
/// Invoked when planes have changed (been added, updated, or removed).
/// </summary>
public event Action<ARPlanesChangedEventArgs> planesChanged;
/// <summary>
/// Attempt to retrieve an existing <see cref="ARPlane"/> by <paramref name="trackableId"/>.
/// </summary>
/// <param name="trackableId">The <see cref="TrackableId"/> of the plane to retrieve.</param>
/// <returns>The <see cref="ARPlane"/> with <paramref name="trackableId"/>, or <c>null</c> if it does not exist.</returns>
public ARPlane GetPlane(TrackableId trackableId) { ARPlane plane; if (m_Trackables.TryGetValue(trackableId, out plane)) return plane;
return null; }
/// <summary>
/// Performs a raycast against all currently tracked planes.
/// </summary>
/// <param name="ray">The ray, in Unity world space, to cast.</param>
/// <param name="trackableTypeMask">A mask of raycast types to perform.</param>
/// <param name="allocator">The <c>Allocator</c> to use when creating the returned <c>NativeArray</c>.</param>
/// <returns>
/// A new <c>NativeArray</c> of raycast results allocated with <paramref name="allocator"/>.
/// The caller owns the memory and is responsible for calling <c>Dispose</c> on the <c>NativeArray</c>.
/// </returns>
/// <seealso cref="ARRaycastManager.Raycast(Ray, List{ARRaycastHit}, TrackableType)"/>
/// <seealso cref="ARRaycastManager.Raycast(Vector2, List{ARRaycastHit}, TrackableType)"/>
public NativeArray<XRRaycastHit> Raycast( Ray ray, TrackableType trackableTypeMask, Allocator allocator) { // No plane types requested; early out.
if ((trackableTypeMask & TrackableType.Planes) == TrackableType.None) return new NativeArray<XRRaycastHit>(0, allocator);
var trackableCollection = trackables;
// Allocate a buffer that is at least large enough to contain a hit against every plane
var hitBuffer = new NativeArray<XRRaycastHit>(trackableCollection.count, Allocator.Temp); try { int count = 0; foreach (var plane in trackableCollection) { TrackableType trackableTypes = TrackableType.None;
var normal = plane.transform.localRotation * Vector3.up; var infinitePlane = new Plane(normal, plane.transform.localPosition); float distance; if (!infinitePlane.Raycast(ray, out distance)) continue;
// Pose in session space
var pose = new Pose( ray.origin + ray.direction * distance, plane.transform.localRotation);
if ((trackableTypeMask & TrackableType.PlaneWithinInfinity) != TrackableType.None) trackableTypes |= TrackableType.PlaneWithinInfinity;
// To test the rest, we need the intersection point in plane space
var hitPositionPlaneSpace3d = Quaternion.Inverse(plane.transform.localRotation) * (pose.position - plane.transform.localPosition); var hitPositionPlaneSpace = new Vector2(hitPositionPlaneSpace3d.x, hitPositionPlaneSpace3d.z);
var estimatedOrWithinBounds = TrackableType.PlaneWithinBounds | TrackableType.PlaneEstimated; if ((trackableTypeMask & estimatedOrWithinBounds) != TrackableType.None) { var differenceFromCenter = hitPositionPlaneSpace - plane.centerInPlaneSpace; if ((Mathf.Abs(differenceFromCenter.x) <= plane.extents.x) && (Mathf.Abs(differenceFromCenter.y) <= plane.extents.y)) { trackableTypes |= (estimatedOrWithinBounds & trackableTypeMask); } }
if ((trackableTypeMask & TrackableType.PlaneWithinPolygon) != TrackableType.None) { if (WindingNumber(hitPositionPlaneSpace, plane.boundary) != 0) trackableTypes |= TrackableType.PlaneWithinPolygon; }
if (trackableTypes != TrackableType.None) { hitBuffer[count++] = new XRRaycastHit( plane.trackableId, pose, distance, trackableTypes); } }
// Finally, copy to return value
var hitResults = new NativeArray<XRRaycastHit>(count, allocator); NativeArray<XRRaycastHit>.Copy(hitBuffer, hitResults, count); return hitResults; } finally { hitBuffer.Dispose(); } }
static float GetCrossDirection(Vector2 a, Vector2 b) { return a.x * b.y - a.y * b.x; }
// See http://geomalgorithms.com/a03-_inclusion.html
static int WindingNumber( Vector2 positionInPlaneSpace, NativeArray<Vector2> boundaryInPlaneSpace) { int windingNumber = 0; Vector2 point = positionInPlaneSpace; for (int i = 0; i < boundaryInPlaneSpace.Length; ++i) { int j = (i + 1) % boundaryInPlaneSpace.Length; Vector2 vi = boundaryInPlaneSpace[i]; Vector2 vj = boundaryInPlaneSpace[j];
if (vi.y <= point.y) { if (vj.y > point.y) // an upward crossing
{ if (GetCrossDirection(vj - vi, point - vi) < 0f) // P left of edge
++windingNumber; } // have a valid up intersect
} else { // y > P.y (no test needed)
if (vj.y <= point.y) // a downward crossing
{ if (GetCrossDirection(vj - vi, point - vi) > 0f) // P right of edge
--windingNumber; } // have a valid down intersect
} }
return windingNumber; }
protected override GameObject GetPrefab() { return m_PlanePrefab; }
protected override void OnBeforeStart() { subsystem.planeDetectionMode = detectionMode; }
protected override void OnAfterSetSessionRelativeData( ARPlane plane, BoundedPlane sessionRelativeData) { ARPlane subsumedByPlane; if (m_Trackables.TryGetValue(sessionRelativeData.subsumedById, out subsumedByPlane)) { plane.subsumedBy = subsumedByPlane; } else { plane.subsumedBy = null; }
plane.UpdateBoundary(subsystem); }
protected override void OnTrackablesChanged( List<ARPlane> added, List<ARPlane> updated, List<ARPlane> removed) { if (planesChanged != null) { planesChanged( new ARPlanesChangedEventArgs( added, updated, removed)); } }
/// <summary>
/// The name to be used for the <c>GameObject</c> whenever a new plane is detected.
/// </summary>
protected override string gameObjectName { get { return "ARPlane"; } }
protected override void OnEnable() { base.OnEnable();
if (subsystem != null) { var raycastManager = GetComponent<ARRaycastManager>(); if (raycastManager != null) raycastManager.RegisterRaycaster(this); } }
protected override void OnDisable() { base.OnDisable();
var raycastManager = GetComponent<ARRaycastManager>(); if (raycastManager != null) raycastManager.UnregisterRaycaster(this); }
static List<Vector2> s_PlaneSpaceBoundary = new List<Vector2>();
const PlaneDetectionMode k_PlaneDetectionModeEverything = (PlaneDetectionMode)(-1); }}
|