// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections.Generic; using Unity.Collections; using Unity.XR.CoreUtils; using UnityEngine; using UnityEngine.XR.ARFoundation; using UnityEngine.XR.ARSubsystems; namespace Microsoft.MixedReality.OpenXR { internal class RaycastSubsystem : XRRaycastSubsystem { public const string Id = "OpenXR Raycasting"; private class OpenXRProvider : Provider { ARPlaneManager m_arPlaneManager = null; #if USE_ARFOUNDATION_5_OR_NEWER private XROrigin m_xrOrigin = null; #else private ARSessionOrigin m_arSessionOrigin = null; #endif public override bool TryAddRaycast(Vector2 screenPoint, float estimatedDistance, out XRRaycast raycast) { Debug.LogError("Persistent raycasts are not supported; use single raycasts instead. More information about single and persistent raycasts is available at https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@4.1/manual/raycast-manager.html#single-raycasts."); raycast = default; return false; } public override bool TryAddRaycast(Ray ray, float estimatedDistance, out XRRaycast raycast) { Debug.LogError("Persistent raycasts are not supported; use single raycasts instead. More information about single and persistent raycasts is available at https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@4.1/manual/raycast-manager.html#single-raycasts."); raycast = default; return false; } public override void RemoveRaycast(TrackableId trackableId) { Debug.LogError("Persistent raycasts are not supported; use single raycasts instead. More information about single and persistent raycasts is available at https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@4.1/manual/raycast-manager.html#single-raycasts."); } public override TrackableChanges GetChanges(XRRaycast defaultRaycast, Allocator allocator) { // Check if the ARPlaneManager is null each frame. // If the app Raycasts before this GetChanges, we won't forward the request to the ARPlaneManager, // but if the raycast request is that early on the first frame, planes likely aren't available yet. if (m_arPlaneManager == null) { // Find the active XROrigin (ARSessionOrigin for ARFoundation < 5.0), and its ARPlaneManager. #if USE_ARFOUNDATION_5_OR_NEWER if (m_xrOrigin == null || !m_xrOrigin.gameObject.activeInHierarchy) { m_xrOrigin = FindObjectUtility.FindFirstObjectByType(false); if (m_xrOrigin != null) { m_arPlaneManager = m_xrOrigin.GetComponent(); } } #else if (m_arSessionOrigin == null || !m_arSessionOrigin.gameObject.activeInHierarchy) { m_arSessionOrigin = FindObjectUtility.FindFirstObjectByType(false); if (m_arSessionOrigin != null) { m_arPlaneManager = m_arSessionOrigin.GetComponent(); } } #endif } return base.GetChanges(defaultRaycast, allocator); } public override void Stop() { m_arPlaneManager = null; // Reset the cached ARPlaneManager. #if USE_ARFOUNDATION_5_OR_NEWER m_xrOrigin = null; // Reset the cached XROrigin. #else m_arSessionOrigin = null; // Reset the cached ARSessionOrigin. #endif } public override void Destroy() { m_arPlaneManager = null; // Reset the cached ARPlaneManager. #if USE_ARFOUNDATION_5_OR_NEWER m_xrOrigin = null; // Reset the cached XROrigin. #else m_arSessionOrigin = null; // Reset the cached ARSessionOrigin. #endif } public override NativeArray Raycast( XRRaycastHit defaultRaycastHit, Ray ray, TrackableType trackableTypeMask, Allocator allocator) { // If we don't have a reference to the ARPlaneManager, we don't raycast. // The ARPlaneManager Raycast is typically only called when it's enabled. if (m_arPlaneManager == null || !m_arPlaneManager.enabled) return new NativeArray(0, allocator); // Use the ARPlaneManager's raycast - this is the only raycast we currently need to support. return m_arPlaneManager.Raycast(ray, trackableTypeMask, allocator); } } [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] static void RegisterDescriptor() { XRRaycastSubsystemDescriptor.RegisterDescriptor(new XRRaycastSubsystemDescriptor.Cinfo { id = Id, providerType = typeof(RaycastSubsystem.OpenXRProvider), subsystemTypeOverride = typeof(RaycastSubsystem), supportedTrackableTypes = TrackableType.Planes, supportsTrackedRaycasts = false, // If this is not supported, ARFoundation will convert the ray to world space, then call the world-based raycast supportsViewportBasedRaycast = false, // If this is not supported, ARFoundation will fallback to the ARPlaneManager's raycast. // On editor remoting, this fallback redirect is broken, so we redirect in Raycast above. supportsWorldBasedRaycast = true }); } }; internal class RaycastSubsystemController : SubsystemController { private static List s_RaycastDescriptors = new List(); public RaycastSubsystemController(IOpenXRContext context) : base(context) { } public override void OnSubsystemCreate(ISubsystemPlugin plugin) { plugin.CreateSubsystem(s_RaycastDescriptors, RaycastSubsystem.Id); } public override void OnSubsystemDestroy(ISubsystemPlugin plugin) { plugin.DestroySubsystem(); } } }