using System.Collections.Generic; using UnityEngine.Events; using UnityEngine.UIElements; using UnityEngine.XR.ARFoundation; namespace UnityEngine.XR.Templates.MR { /// /// Utility class used to control various AR features like occlusion, AR bounding boxes, and AR planes. /// public class ARFeatureController : MonoBehaviour { [SerializeField, Tooltip("AR Plane Manager that is in charge of passthrough.")] ARCameraManager m_ARCameraManager; /// /// AR Camera Manager that is in charge of passthrough. /// public ARCameraManager arCameraManager { get => m_ARCameraManager; set => m_ARCameraManager = value; } [SerializeField, Tooltip("AR Occlusion Manager that is in charge of changing MR Occlusion Features.")] OcclusionManager m_OcclusionManager; /// /// AR Occlusion Manager that is in charge of changing MR Occlusion Features. /// public OcclusionManager occlusionManager { get => m_OcclusionManager; set => m_OcclusionManager = value; } [SerializeField, Tooltip("AR Plane Manager that is in charge of spawning new AR Plane prefabs into the scene.")] ARPlaneManager m_PlaneManager; /// /// AR Plane Manager that is in charge of spawning new AR Plane prefabs into the scene. /// public ARPlaneManager planeManager { get => m_PlaneManager; set => m_PlaneManager = value; } [SerializeField, Tooltip("Toggle that dictates whether AR Planes should be visualized at runtime.")] bool m_PlaneVisualsEnabled = true; /// /// Toggle that dictates whether AR Planes should be visualized at runtime. /// public bool PlaneVisualsEnabled => m_PlaneVisualsEnabled; [SerializeField, Tooltip("AR Bounding Box Manager that is in charge of spawning new AR Bounding Box prefabs into the scene")] ARBoundingBoxManager m_BoundingBoxManager; /// /// AR Bounding Box Manager that is in charge of spawning new AR Bounding Box prefabs into the scene. /// public ARBoundingBoxManager BoundingBoxManager { get => m_BoundingBoxManager; set => m_BoundingBoxManager = value; } [SerializeField, Tooltip("Toggle that dictates whether AR Bounding Boxes should be visualized at runtime.")] bool m_BoundingBoxVisualsEnabled = true; /// /// Toggle that dictates whether AR Bounding Boxes should be visualized at runtime. /// public bool BoundingBoxVisualsEnabled => m_BoundingBoxVisualsEnabled; [SerializeField, Tooltip("Toggle that dictates whether AR Bounding Box visualizations should show additional debug information.")] bool m_BoundingBoxDebugInfoEnabled = true; /// /// Toggle that dictates whether AR Bounding Box visualizations should show additional debug information. /// public bool boundingBoxDebugInfoEnabled => m_BoundingBoxDebugInfoEnabled; [Header("Feature Changed Events")] [SerializeField] UnityEvent m_OnARPassthroughFeatureChanged = new UnityEvent(); public UnityEvent onARPassthroughFeatureChanged => m_OnARPassthroughFeatureChanged; [SerializeField] UnityEvent m_OnARPlaneFeatureChanged = new UnityEvent(); public UnityEvent onARPlaneFeatureChanged => m_OnARPlaneFeatureChanged; [SerializeField] UnityEvent m_OnARPlaneFeatureVisualizationChanged = new UnityEvent(); public UnityEvent onARPlaneFeatureVisualizationChanged => m_OnARPlaneFeatureVisualizationChanged; [SerializeField] UnityEvent m_OnARBoundingBoxFeatureChanged = new UnityEvent(); public UnityEvent onARBoundingBoxFeatureChanged => m_OnARBoundingBoxFeatureChanged; [SerializeField] UnityEvent m_OnARBoundingBoxFeatureVisualizationChanged = new UnityEvent(); public UnityEvent onARBoundingBoxFeatureVisualizationChanged => m_OnARBoundingBoxFeatureVisualizationChanged; [SerializeField] UnityEvent m_OnARBoundingBoxFeatureDebugVisualizationChanged = new UnityEvent(); public UnityEvent onARBoundingBoxFeatureDebugVisualizationChanged => m_OnARBoundingBoxFeatureDebugVisualizationChanged; /// /// Allows access to easily see if the AR Features are enabled and there is at least one bounding box /// /// Will return True if there is 1 or more AR Bounding Boxes found in the AR Scene. public bool HasBoundingBoxes() => m_BoundingBoxManager != null && m_BoundingBoxManager.trackables.count > 0; bool m_BoundingBoxManagerEnabled; bool m_PlaneManagerEnabled; readonly List m_ARPlanes = new List(); readonly Dictionary m_ARPlaneMeshVisualizers = new Dictionary(); readonly List m_ARBoundingBoxes = new List(); readonly Dictionary m_ARBoundingBoxVisualizers = new Dictionary(); /// /// Functionally turns AR Passthrough on and off in the scene. /// /// Whether to enable or disable passthrough. public void TogglePassthrough(bool enabled) { if (m_ARCameraManager == null) return; m_ARCameraManager.enabled = enabled; m_OnARPassthroughFeatureChanged?.Invoke(enabled); } /// /// Functionally turns AR Planes on and off in a scene. /// /// Whether to enable or disable the currently detected planes. public void TogglePlanes(bool enabled) { if (m_PlaneManager == null) return; m_PlaneManagerEnabled = enabled; m_OnARPlaneFeatureChanged?.Invoke(m_PlaneManagerEnabled); // Ensure listener is removed m_PlaneManager.trackablesChanged.RemoveListener(OnPlaneChanged); if (m_PlaneManagerEnabled) { m_PlaneManager.enabled = m_PlaneManagerEnabled; m_PlaneManager.SetTrackablesActive(m_PlaneManagerEnabled); m_PlaneManager.trackablesChanged.AddListener(OnPlaneChanged); } else { m_PlaneManager.SetTrackablesActive(m_PlaneManagerEnabled); m_PlaneManager.enabled = m_PlaneManagerEnabled; } } /// /// Toggles the AR plane visualizations in a scene. /// /// If , AR plane visualizations will be enabled. Otherwise AR plane visualizations be disabled. public void TogglePlaneVisualization(bool enabled) { if (m_PlaneManager == null) return; m_PlaneVisualsEnabled = enabled; m_OnARPlaneFeatureVisualizationChanged?.Invoke(m_PlaneVisualsEnabled); var trackables = m_PlaneManager.trackables; if (trackables.count != m_ARPlanes.Count) { RefreshAllPlanes(); } else { foreach (var visualizer in m_ARPlaneMeshVisualizers.Values) { visualizer.enabled = m_PlaneVisualsEnabled; } } } /// /// Functionally turns AR Bounding Boxes on and off in a scene. /// /// Whether to enable or disable the currently detected bounding boxes. public void ToggleBoundingBoxes(bool enabled) { if (m_BoundingBoxManager == null) return; m_BoundingBoxManagerEnabled = enabled; m_OnARBoundingBoxFeatureChanged?.Invoke(m_BoundingBoxManagerEnabled); // Ensure listener is removed m_BoundingBoxManager.trackablesChanged.RemoveListener(OnBoundingBoxesChanged); if (m_BoundingBoxManagerEnabled) { m_BoundingBoxManager.enabled = m_BoundingBoxManagerEnabled; m_BoundingBoxManager.SetTrackablesActive(m_BoundingBoxManagerEnabled); m_BoundingBoxManager.trackablesChanged.AddListener(OnBoundingBoxesChanged); } else { m_BoundingBoxManager.SetTrackablesActive(m_BoundingBoxManagerEnabled); m_BoundingBoxManager.enabled = m_BoundingBoxManagerEnabled; } } /// /// Toggles the AR Bounding Boxes visualizations in a scene. /// /// If , AR Bounding Boxes visualizations will be enabled. Otherwise AR Bounding Boxes visualizations be disabled. public void ToggleBoundingBoxVisualization(bool enabled) { if (m_BoundingBoxManager == null) return; m_BoundingBoxVisualsEnabled = enabled; m_OnARBoundingBoxFeatureVisualizationChanged?.Invoke(m_BoundingBoxVisualsEnabled); var trackables = m_BoundingBoxManager.trackables; if (trackables.count != m_ARBoundingBoxes.Count) { RefreshAllBoundingBoxes(); } else { foreach (var visualizer in m_ARBoundingBoxVisualizers.Values) { visualizer.enabled = m_BoundingBoxVisualsEnabled; visualizer.ShowDebugInfoCanvas(m_BoundingBoxVisualsEnabled && m_BoundingBoxDebugInfoEnabled); } } } /// /// Toggles the visualization of the debug information for AR Bounding Boxes. /// /// If , debug information will be shown for AR Bounding Boxes. Otherwise, debug information will not be shown. public void ToggleDebugInfo(bool enabled) { if (m_BoundingBoxManager == null) return; m_BoundingBoxDebugInfoEnabled = enabled; m_OnARBoundingBoxFeatureDebugVisualizationChanged?.Invoke(m_BoundingBoxDebugInfoEnabled); // If general bounding box visuals are not enabled, do not enable the debug info. if (!m_BoundingBoxVisualsEnabled) return; var trackables = m_BoundingBoxManager.trackables; foreach (var trackable in trackables) { if (trackable.TryGetComponent(out ARBoundingBoxDebugVisualizer visualizer)) { visualizer.ShowDebugInfoCanvas(m_BoundingBoxDebugInfoEnabled); } } } void OnPlaneChanged(ARTrackablesChangedEventArgs eventArgs) { if (eventArgs.added.Count > 0) { foreach (var plane in eventArgs.added) { m_ARPlanes.Add(plane); if (plane.TryGetComponent(out var visualizer)) { m_ARPlaneMeshVisualizers[plane] = visualizer; visualizer.enabled = m_PlaneVisualsEnabled; } } } if (eventArgs.removed.Count > 0) { foreach (var plane in eventArgs.removed) { var planeGameObject = plane.Value; if (planeGameObject == null) continue; if (m_ARPlanes.Contains(planeGameObject)) m_ARPlanes.Remove(planeGameObject); if (m_ARPlaneMeshVisualizers.ContainsKey(planeGameObject)) m_ARPlaneMeshVisualizers.Remove(planeGameObject); } } // Fallback if the counts do not match after an update if (m_PlaneManager.trackables.count != m_ARPlanes.Count) { RefreshAllPlanes(); } } void RefreshAllPlanes() { m_ARPlanes.Clear(); m_ARPlaneMeshVisualizers.Clear(); foreach (var plane in m_PlaneManager.trackables) { m_ARPlanes.Add(plane); if (plane.TryGetComponent(out var visualizer)) { m_ARPlaneMeshVisualizers[plane] = visualizer; visualizer.enabled = m_PlaneVisualsEnabled; } } } void OnBoundingBoxesChanged(ARTrackablesChangedEventArgs eventArgs) { if (eventArgs.added.Count > 0) { foreach (var box in eventArgs.added) { m_ARBoundingBoxes.Add(box); if (box.TryGetComponent(out var visualizer)) { m_ARBoundingBoxVisualizers[box] = visualizer; visualizer.enabled = m_BoundingBoxVisualsEnabled; visualizer.ShowDebugInfoCanvas(m_BoundingBoxDebugInfoEnabled && m_BoundingBoxDebugInfoEnabled); } } } if (eventArgs.removed.Count > 0) { foreach (var box in eventArgs.removed) { var boxGameObject = box.Value; if (boxGameObject == null) continue; if (m_ARBoundingBoxes.Contains(boxGameObject)) m_ARBoundingBoxes.Remove(boxGameObject); if (m_ARBoundingBoxVisualizers.ContainsKey(boxGameObject)) m_ARBoundingBoxVisualizers.Remove(boxGameObject); } } // Fallback if the counts do not match after an update if (m_BoundingBoxManager.trackables.count != m_ARBoundingBoxes.Count) { RefreshAllBoundingBoxes(); } } void RefreshAllBoundingBoxes() { m_ARBoundingBoxes.Clear(); m_ARBoundingBoxVisualizers.Clear(); foreach (var box in m_BoundingBoxManager.trackables) { m_ARBoundingBoxes.Add(box); if (box.TryGetComponent(out var visualizer)) { m_ARBoundingBoxVisualizers[box] = visualizer; visualizer.enabled = m_BoundingBoxVisualsEnabled; visualizer.ShowDebugInfoCanvas(m_BoundingBoxDebugInfoEnabled && m_BoundingBoxDebugInfoEnabled); } } } } }