using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine.XR.ARSubsystems;
using Unity.XR.CoreUtils;
namespace UnityEngine.XR.ARFoundation
{
///
/// A [trackable manager](xref:arfoundation-managers#trackables-and-trackable-managers) that detects and tracks
/// flat surfaces in the physical environment. Add this component to your XR Origin GameObject to enable plane
/// detection in your app.
///
///
/// Related information: AR Plane Manager component
///
[DefaultExecutionOrder(ARUpdateOrder.k_PlaneManager)]
[DisallowMultipleComponent]
[RequireComponent(typeof(XROrigin))]
[AddComponentMenu("XR/AR Foundation/AR Plane Manager")]
[HelpURL("features/plane-detection/arplanemanager")]
public sealed class ARPlaneManager : ARTrackableManager<
XRPlaneSubsystem,
XRPlaneSubsystemDescriptor,
XRPlaneSubsystem.Provider,
BoundedPlane,
ARPlane>, IRaycaster
{
[SerializeField]
[Tooltip(
"If not null, this prefab is instantiated for each detected plane. " +
"If the prefab does not contain an AR Plane component, ARPlaneManager will add one.")]
GameObject m_PlanePrefab;
///
/// Get or set the prefab to instantiate for each detected plane. Can be .
///
public GameObject planePrefab
{
get => m_PlanePrefab;
set => m_PlanePrefab = value;
}
[SerializeField, PlaneDetectionModeMask]
[Tooltip("The types of planes to detect.")]
PlaneDetectionMode m_DetectionMode = (PlaneDetectionMode)(-1);
///
/// Get or set the requested plane detection mode.
///
public PlaneDetectionMode requestedDetectionMode
{
get => subsystem?.requestedPlaneDetectionMode ?? m_DetectionMode;
set
{
m_DetectionMode = value;
if (enabled && subsystem != null)
{
subsystem.requestedPlaneDetectionMode = value;
}
}
}
///
/// Get the current plane detection mode in use by the subsystem.
///
public PlaneDetectionMode currentDetectionMode => subsystem?.currentPlaneDetectionMode ?? PlaneDetectionMode.None;
///
/// Invoked when planes have changed (been added, updated, or removed).
///
[Obsolete("planesChanged has been deprecated in AR Foundation version 6.0. Use trackablesChanged instead.", false)]
public event Action planesChanged;
///
/// Attempt to retrieve an existing by .
///
/// The of the plane to retrieve.
/// The with , or null if it does not exist.
public ARPlane GetPlane(TrackableId trackableId) => m_Trackables.TryGetValue(trackableId, out ARPlane plane) ? plane : null;
///
/// Performs a raycast against all currently tracked planes.
///
/// The ray, in Unity world space, to cast.
/// A mask of raycast types to perform.
/// The Allocator to use when creating the returned NativeArray.
///
/// A new NativeArray of raycast results allocated with .
/// The caller owns the memory and is responsible for calling Dispose on the NativeArray.
///
///
///
public NativeArray Raycast(
Ray ray,
TrackableType trackableTypeMask,
Allocator allocator)
{
// No plane types requested; early out.
if ((trackableTypeMask & TrackableType.Planes) == TrackableType.None)
return new NativeArray(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(trackableCollection.count, Allocator.Temp);
try
{
int count = 0;
foreach (var plane in trackableCollection)
{
TrackableType trackableTypes = TrackableType.None;
var t = plane.transform;
var normal = t.localRotation * Vector3.up;
var infinitePlane = new Plane(normal, t.localPosition);
if (!infinitePlane.Raycast(ray, out var 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);
const TrackableType 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(count, allocator);
NativeArray.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 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;
}
///
/// Get the prefab to instantiate for each detected plane. Can be .
///
/// The Prefab which will be instantiated for each .
protected override GameObject GetPrefab() => m_PlanePrefab;
///
/// Invoked just before `Start`ing the plane subsystem. Used to set the subsystem's
/// `requestedPlaneDetectionMode`.
///
protected override void OnBeforeStart()
{
subsystem.requestedPlaneDetectionMode = m_DetectionMode;
}
///
/// Invoked just after each is updated.
///
/// The being updated.
/// The new data associated with the plane. All spatial
/// data is is session-relative space.
protected override void OnAfterSetSessionRelativeData(
ARPlane plane,
BoundedPlane sessionRelativeData)
{
plane.subsumedBy = m_Trackables.GetValueOrDefault(sessionRelativeData.subsumedById);
plane.UpdateBoundary(subsystem);
}
///
/// Invoked when the base class detects trackable changes.
///
/// The list of added s.
/// The list of updated s.
/// The list of removed s.
[Obsolete("OnTrackablesChanged() has been deprecated in AR Foundation version 6.0.", false)]
protected override void OnTrackablesChanged(
List added,
List updated,
List removed)
{
if (trackablesChanged != null)
{
using (new ScopedProfiler("OnPlanesChanged"))
planesChanged?.Invoke(new ARPlanesChangedEventArgs(added, updated, removed));
}
}
///
/// The name to be used for the instantiated GameObject whenever a new plane is detected.
///
protected override string gameObjectName => "ARPlane";
///
/// Invoked when Unity enables this `MonoBehaviour`. Used to register with the .
///
protected override void OnEnable()
{
base.OnEnable();
if (subsystem != null)
{
var raycastManager = GetComponent();
if (raycastManager != null)
raycastManager.RegisterRaycaster(this);
}
}
///
/// Invoked when this component is disabled. Used to unregister with the .
///
protected override void OnDisable()
{
base.OnDisable();
var raycastManager = GetComponent();
if (raycastManager != null)
raycastManager.UnregisterRaycaster(this);
}
#if UNITY_EDITOR
void OnValidate()
{
requestedDetectionMode = m_DetectionMode;
}
#endif // UNITY_EDITOR
}
}