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