using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine.Serialization;
using UnityEngine.XR.ARSubsystems;
namespace UnityEngine.XR.ARFoundation
{
///
/// A manager for s. Creates, updates, and removes
/// GameObjects in response to detected surfaces in the physical
/// environment.
///
[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;
///
/// Getter/setter for the Plane Prefab.
///
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;
///
/// Get or set the PlaneDetectionMode to use for plane detection.
///
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;
}
}
///
/// Invoked when planes have changed (been added, updated, or removed).
///
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)
{
ARPlane plane;
if (m_Trackables.TryGetValue(trackableId, out plane))
return plane;
return 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 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(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;
}
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 added,
List updated,
List removed)
{
if (planesChanged != null)
{
planesChanged(
new ARPlanesChangedEventArgs(
added,
updated,
removed));
}
}
///
/// The name to be used for the GameObject whenever a new plane is detected.
///
protected override string gameObjectName
{
get { return "ARPlane"; }
}
protected override void OnEnable()
{
base.OnEnable();
if (subsystem != null)
{
var raycastManager = GetComponent();
if (raycastManager != null)
raycastManager.RegisterRaycaster(this);
}
}
protected override void OnDisable()
{
base.OnDisable();
var raycastManager = GetComponent();
if (raycastManager != null)
raycastManager.UnregisterRaycaster(this);
}
static List s_PlaneSpaceBoundary = new List();
const PlaneDetectionMode k_PlaneDetectionModeEverything = (PlaneDetectionMode)(-1);
}
}