using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine.XR.ARSubsystems;
namespace UnityEngine.XR.ARFoundation
{
///
/// Manages an XRRaycastSubsystem, exposing raycast functionality in ARFoundation. Use this component
/// to raycast against trackables (i.e., detected features in the physical environment) when they do not have
/// a presence in the Physics world.
///
[DisallowMultipleComponent]
[RequireComponent(typeof(ARSessionOrigin))]
[HelpURL("https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@3.0/api/UnityEngine.XR.ARFoundation.ARRaycastManager.html")]
public sealed class ARRaycastManager : SubsystemLifecycleManager
{
///
/// Cast a ray from a point in screen space against trackables, i.e., detected features such as planes.
///
/// The point, in device screen pixels, from which to cast.
/// Contents are replaced with the raycast results, if successful.
/// (Optional) The types of trackables to cast against.
/// True if the raycast hit a trackable in the
public bool Raycast(
Vector2 screenPoint,
List hitResults,
TrackableType trackableTypes = TrackableType.All)
{
if (subsystem == null)
return false;
if (hitResults == null)
throw new ArgumentNullException("hitResults");
var nativeHits = m_RaycastViewportDelegate(screenPoint, trackableTypes, Allocator.Temp);
var originTransform = m_SessionOrigin.camera != null ? m_SessionOrigin.camera.transform : m_SessionOrigin.trackablesParent;
return TransformAndDisposeNativeHitResults(nativeHits, hitResults, originTransform.position);
}
///
/// Cast a Ray against trackables, i.e., detected features such as planes.
///
/// The Ray, in Unity world space, to cast.
/// Contents are replaced with the raycast results, if successful.
/// (Optional) The types of trackables to cast against.
/// True if the raycast hit a trackable in the
public bool Raycast(
Ray ray,
List hitResults,
TrackableType trackableTypes = TrackableType.All)
{
if (subsystem == null)
return false;
if (hitResults == null)
throw new ArgumentNullException("hitResults");
var sessionSpaceRay = m_SessionOrigin.trackablesParent.InverseTransformRay(ray);
var nativeHits = m_RaycastRayDelegate(sessionSpaceRay, trackableTypes, Allocator.Temp);
return TransformAndDisposeNativeHitResults(nativeHits, hitResults, ray.origin);
}
static void TransformAndSortRaycastResults(
Transform transform,
NativeArray nativeHits,
List managedHits,
Vector3 rayOrigin)
{
foreach (var nativeHit in nativeHits)
{
float distanceInWorldSpace = (nativeHit.pose.position - rayOrigin).magnitude;
managedHits.Add(new ARRaycastHit(nativeHit, distanceInWorldSpace, transform));
}
}
///
/// Allows AR managers to register themselves as a raycaster.
/// Raycasters be used as a fallback method if the AR platform does
/// not support raycasting using arbitrary Rays.
///
/// A raycaster implementing the IRaycast interface.
internal void RegisterRaycaster(IRaycaster raycaster)
{
ConstructIfNecessary();
if (!m_Raycasters.Contains(raycaster))
m_Raycasters.Add(raycaster);
}
///
/// Unregisters a raycaster previously registered with .
///
/// A raycaster to use in the fallback case.
internal void UnregisterRaycaster(IRaycaster raycaster)
{
if (m_Raycasters != null)
m_Raycasters.Remove(raycaster);
}
protected override void OnAfterStart()
{
if (subsystem.SubsystemDescriptor.supportsViewportBasedRaycast)
{
m_RaycastViewportDelegate = RaycastViewport;
}
else
{
m_RaycastViewportDelegate = RaycastViewportAsRay;
}
if (subsystem.SubsystemDescriptor.supportsWorldBasedRaycast)
{
m_RaycastRayDelegate = RaycastRay;
}
else
{
m_RaycastRayDelegate = RaycastFallback;
}
var raycasters = GetComponents(typeof(IRaycaster));
foreach (var raycaster in raycasters)
RegisterRaycaster((IRaycaster)raycaster);
}
NativeArray RaycastViewportAsRay(
Vector2 screenPoint,
TrackableType trackableTypeMask,
Allocator allocator)
{
if (m_SessionOrigin.camera == null)
return new NativeArray(0, allocator);
var worldSpaceRay = m_SessionOrigin.camera.ScreenPointToRay(screenPoint);
var sessionSpaceRay = m_SessionOrigin.trackablesParent.InverseTransformRay(worldSpaceRay);
return m_RaycastRayDelegate(sessionSpaceRay, trackableTypeMask, allocator);
}
NativeArray RaycastViewport(
Vector2 screenPoint,
TrackableType trackableTypeMask,
Allocator allocator)
{
screenPoint.x = Mathf.Clamp01(screenPoint.x / Screen.width);
screenPoint.y = Mathf.Clamp01(screenPoint.y / Screen.height);
return subsystem.Raycast(screenPoint, trackableTypeMask, allocator);
}
NativeArray RaycastRay(
Ray ray,
TrackableType trackableTypeMask,
Allocator allocator)
{
return subsystem.Raycast(ray, trackableTypeMask, allocator);
}
static int RaycastHitComparer(ARRaycastHit lhs, ARRaycastHit rhs)
{
return lhs.CompareTo(rhs);
}
NativeArray RaycastFallback(
Ray ray,
TrackableType trackableTypeMask,
Allocator allocator)
{
s_NativeRaycastHits.Clear();
int count = 0;
foreach (var raycaster in m_Raycasters)
{
var hits = raycaster.Raycast(ray, trackableTypeMask, Allocator.Temp);
if (hits.IsCreated)
{
s_NativeRaycastHits.Add(hits);
count += hits.Length;
}
}
var allHits = new NativeArray(count, allocator);
int dstIndex = 0;
foreach (var hitArray in s_NativeRaycastHits)
{
NativeArray.Copy(hitArray, 0, allHits, dstIndex, hitArray.Length);
hitArray.Dispose();
dstIndex += hitArray.Length;
}
return allHits;
}
bool TransformAndDisposeNativeHitResults(
NativeArray nativeHits,
List managedHits,
Vector3 rayOrigin)
{
managedHits.Clear();
if (!nativeHits.IsCreated)
return false;
try
{
// Results are in "trackables space", so transform results back into world space
TransformAndSortRaycastResults(m_SessionOrigin.trackablesParent, nativeHits, managedHits, rayOrigin);
managedHits.Sort(s_RaycastHitComparer);
return managedHits.Count > 0;
}
finally
{
nativeHits.Dispose();
}
}
void ConstructIfNecessary()
{
if (m_Raycasters == null)
m_Raycasters = new List();
}
void Awake()
{
m_SessionOrigin = GetComponent();
ConstructIfNecessary();
}
static Comparison s_RaycastHitComparer = RaycastHitComparer;
static List> s_NativeRaycastHits = new List>();
ARSessionOrigin m_SessionOrigin;
Func> m_RaycastViewportDelegate;
Func> m_RaycastRayDelegate;
List m_Raycasters;
}
}