using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using UnityEngine.Rendering;
using UnityEngine.Serialization;
using Unity.Collections;
using Unity.XR.CoreUtils;
using UnityEngine.XR.ARSubsystems;
using UnityEngine.XR.Management;
namespace UnityEngine.XR.ARFoundation
{
///
/// Menu that is added to a scene to surface tracking data and visualize trackables in order to aid in debugging.
///
[RequireComponent(typeof(Canvas))]
[HelpURL("debug-ar-scenes")]
public class ARDebugMenu : MonoBehaviour
{
///
/// The render mode of the debug menu's canvas.
///
public enum DebugMenuRenderMode
{
///
/// Automated mode that sets the canvas render mode to screen space on mobile platforms
/// and world space on non-mobile platforms.
///
Auto,
///
/// Overlay screen space render mode for the canvas.
///
ScreenSpace,
///
/// World space render mode for the canvas.
///
WorldSpace,
}
[SerializeField]
[Tooltip("A debug prefab that visualizes the position of the XROrigin.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
GameObject m_OriginAxisPrefab;
///
/// Specifies a debug prefab that will be attached to the .
///
///
/// A debug prefab that will be attached to the XR origin.
///
public GameObject originAxisPrefab
{
get => m_OriginAxisPrefab;
set => m_OriginAxisPrefab = value;
}
[SerializeField]
[Tooltip("A particle system to represent ARPointClouds.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
ParticleSystem m_PointCloudParticleSystem;
///
/// Specifies a particle system to visualize an .
///
///
/// A particle system that will visualize point clouds.
///
public ParticleSystem pointCloudParticleSystem
{
get => m_PointCloudParticleSystem;
set => m_PointCloudParticleSystem = value;
}
[SerializeField]
[Tooltip("A line renderer used to outline the ARPlanes in a scene.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
LineRenderer m_LineRendererPrefab;
///
/// Specifies the line renderer that will be used to outline planes in the scene.
///
///
/// A line renderer used to outline planes in the scene.
///
public LineRenderer lineRendererPrefab
{
get => m_LineRendererPrefab;
set => m_LineRendererPrefab = value;
}
[SerializeField]
[Tooltip("A debug prefab that visualizes the position of ARAnchors in a scene.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
GameObject m_AnchorPrefab;
///
/// Specifies a debug prefab that will be attached to an .
///
///
/// A debug prefab that will be attached to the AR anchors in a scene.
///
public GameObject anchorPrefab
{
get => m_AnchorPrefab;
set => m_AnchorPrefab = value;
}
[SerializeField]
[Tooltip("The button that displays the AR Debug Menu's info sub-menu.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
Button m_DisplayInfoMenuButton;
///
/// The button that displays the AR Debug Menu's info sub-menu.
///
///
/// A button that will be used to display the AR Debug Menu's info sub-menu.
///
public Button displayInfoMenuButton
{
get => m_DisplayInfoMenuButton;
set => m_DisplayInfoMenuButton = value;
}
[SerializeField]
[Tooltip("The button that displays the AR Debug Menu's configuration sub-menu.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
Button m_DisplayConfigurationsMenuButton;
///
/// The button that displays the AR Debug Menu's session configuration sub-menu.
///
///
/// A button that will be used to display the AR Debug Menu's session configuration sub-menu.
///
public Button displayConfigurationsMenuButton
{
get => m_DisplayConfigurationsMenuButton;
set => m_DisplayConfigurationsMenuButton = value;
}
[SerializeField]
[Tooltip("The button that displays the AR Debug Menu's camera configuration sub-menu.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
Button m_DisplayCameraConfigurationsMenuButton;
///
/// The button that displays the AR Debug Menu's camera configuration sub-menu.
///
///
/// A button that will be used to display the AR Debug Menu's camera configuration sub-menu.
///
public Button displayCameraConfigurationsMenuButton
{
get => m_DisplayCameraConfigurationsMenuButton;
set => m_DisplayCameraConfigurationsMenuButton = value;
}
[SerializeField]
[Tooltip("The button that displays the AR Debug Menu's debug options sub-menu.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
Button m_DisplayDebugOptionsMenuButton;
///
/// The button that displays the AR Debug Menu's debug options sub-menu.
///
///
/// A button that will be used to display the AR Debug Menu's debug options sub-menu.
///
public Button displayDebugOptionsMenuButton
{
get => m_DisplayDebugOptionsMenuButton;
set => m_DisplayDebugOptionsMenuButton = value;
}
[SerializeField]
[Tooltip("The menu that contains debug info such as current FPS and tracking state.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
GameObject m_InfoMenu;
///
/// The menu that contains debug info such as current FPS and tracking state.
///
///
/// A menu that will be used to display debug info such as current FPS and tracking state.
///
public GameObject infoMenu
{
get => m_InfoMenu;
set => m_InfoMenu = value;
}
[SerializeField]
[Tooltip("The menu that displays available camera configurations for the current session configuration.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
GameObject m_CameraConfigurationMenu;
///
/// The menu that displays available s for the current session configuration.
///
///
/// A menu that will be used to display available camera configurations for the current session configuration.
///
public GameObject cameraConfigurationMenu
{
get => m_CameraConfigurationMenu;
set => m_CameraConfigurationMenu = value;
}
[SerializeField]
[Tooltip("The menu that displays available session configurations for the current platform.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
GameObject m_ConfigurationMenu;
///
/// The menu that displays available s for the current platform.
///
///
/// A menu that will be used to display available session configurations for the current platform.
///
public GameObject configurationMenu
{
get => m_ConfigurationMenu;
set => m_ConfigurationMenu = value;
}
[SerializeField]
[Tooltip("The root of the menu where the available configurations for the current platform will be displayed and anchored.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
GameObject m_ConfigurationMenuRoot;
///
/// The root of the menu that displays available s for the current platform. This helps center the data in the generated menu.
///
///
/// The root of the menu where the available configurations for the current platform will be displayed and anchored.
///
public GameObject configurationMenuRoot
{
get => m_ConfigurationMenuRoot;
set => m_ConfigurationMenuRoot = value;
}
[SerializeField]
[Tooltip("The menu that provides buttons for visualizing different trackables for debugging purposes.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
GameObject m_DebugOptionsMenu;
///
/// The menu that provides buttons for visualizing different trackables for debugging purposes.
///
///
/// A menu that will be used to display different trackables for debugging purposes.
///
public GameObject debugOptionsMenu
{
get => m_DebugOptionsMenu;
set => m_DebugOptionsMenu = value;
}
[SerializeField]
[Tooltip("A menu that displays an informative warning messages.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
GameObject m_DebugOptionsToastMenu;
///
/// The menu that displays informative warning messages.
///
///
/// A menu that displays an informative warning messages.
///
public GameObject debugOptionsToastMenu
{
get => m_DebugOptionsToastMenu;
set => m_DebugOptionsToastMenu = value;
}
[SerializeField, FormerlySerializedAs("m_ShowSessionOriginButton")]
[Tooltip("The button that displays the XR origin prefab.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
DebugSlider m_ShowOriginButton;
///
/// The button that displays the XR origin prefab.
///
///
/// A button that will be used to display the XR origin prefab.
///
public DebugSlider showOriginButton
{
get => m_ShowOriginButton;
set => m_ShowOriginButton = value;
}
[SerializeField]
[Tooltip("The button that displays detected AR planes in the scene.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
DebugSlider m_ShowPlanesButton;
///
/// The button that displays detected AR planes in the scene.
///
///
/// A button that will be used to display detected AR planes in the scene.
///
public DebugSlider showPlanesButton
{
get => m_ShowPlanesButton;
set => m_ShowPlanesButton = value;
}
[SerializeField]
[Tooltip("The button that displays anchors in the scene.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
DebugSlider m_ShowAnchorsButton;
///
/// The button that displays anchors in the scene.
///
///
/// A button that will be used to display anchors in the scene.
///
public DebugSlider showAnchorsButton
{
get => m_ShowAnchorsButton;
set => m_ShowAnchorsButton = value;
}
[SerializeField]
[Tooltip("The button that displays detected point clouds in the scene.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
DebugSlider m_ShowPointCloudsButton;
///
/// The button that displays detected point clouds in the scene.
///
///
/// A button that will be used to display detected point clouds in the scene.
///
public DebugSlider showPointCloudsButton
{
get => m_ShowPointCloudsButton;
set => m_ShowPointCloudsButton = value;
}
[SerializeField]
[Tooltip("The text object that will display current FPS.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
Text m_FpsLabel;
///
/// The text object that will display current FPS.
///
///
/// A text object that will display current FPS.
///
public Text fpsLabel
{
get => m_FpsLabel;
set => m_FpsLabel = value;
}
[SerializeField]
[Tooltip("The text object that will display current tracking mode.\n For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.")]
Text m_TrackingModeLabel;
///
/// The text object that will display current tracking mode.
///
///
/// A text object that will display current tracking mode.
///
public Text trackingModeLabel
{
get => m_TrackingModeLabel;
set => m_TrackingModeLabel = value;
}
[SerializeField]
[Tooltip("The checkmark texture that will be used in the configuration submenu to display available configurations.")]
Texture2D m_CheckMarkTexture;
///
/// The checkmark texture that will be used in the submenu to display available configurations.
///
///
/// A checkmark texture that will be used in the configuration submenu to display available configurations.
///
public Texture2D checkMarkTexture
{
get => m_CheckMarkTexture;
set => m_CheckMarkTexture = value;
}
[SerializeField]
[Tooltip("The side bar that contains the buttons to the status info, configurations, and debug options menus.")]
GameObject m_Toolbar;
///
/// The side bar that contains the buttons to the status info, configurations, and debug options menus.
///
///
/// A side bar that contains the buttons to the status info, configurations, and debug options menus.
///
public GameObject toolbar
{
get => m_Toolbar;
set => m_Toolbar = value;
}
[SerializeField]
[Tooltip("The font that will be used for the generated parts of the menu.")]
Font m_MenuFont;
///
/// The font that will be used for the generated parts of the menu.
///
///
/// A font that will be used for the generated parts of the menu.
///
public Font menuFont
{
get => m_MenuFont;
set => m_MenuFont = value;
}
[SerializeField]
[Tooltip("The label that will display the resolution of the current camera configuration.")]
Text m_CameraResolutionLabel;
///
/// The label that will display the resolution of the current camera configuration.
///
///
/// A label that will display the resolution of the current camera configuration.
///
public Text cameraResolutionLabel
{
get => m_CameraResolutionLabel;
set => m_CameraResolutionLabel = value;
}
[SerializeField]
[Tooltip("The label that will display the frame rate of the current camera configuration.")]
Text m_CameraFrameRateLabel;
///
/// The label that will display the frame rate of the current camera configuration.
///
///
/// A label that will display the frame rate of the current camera configuration.
///
public Text cameraFrameRateLabel
{
get => m_CameraFrameRateLabel;
set => m_CameraFrameRateLabel = value;
}
[SerializeField]
[Tooltip("The label that will display whether depth sensor is supported in the current camera configuration.")]
Text m_CameraDepthSensorLabel;
///
/// The label that will display whether depth sensor is supported in the current camera configuration.
///
///
/// A label that will display whether depth sensor is supported in the current camera configuration.
///
public Text cameraDepthSensorLabel
{
get => m_CameraDepthSensorLabel;
set => m_CameraDepthSensorLabel = value;
}
[SerializeField]
[Tooltip("The dropdown that will display the list of currently available camera configurations.")]
Dropdown m_CameraConfigurationDropdown;
///
/// The dropdown that will display the list of currently available camera configurations.
///
///
/// A dropdown that will display the list of currently available camera configurations.
///
public Dropdown cameraConfigurationDropdown
{
get => m_CameraConfigurationDropdown;
set => m_CameraConfigurationDropdown = value;
}
[SerializeField]
[Tooltip("The render mode of the debug menu's canvas.")]
DebugMenuRenderMode m_DebugMenuRenderMode = DebugMenuRenderMode.Auto;
///
/// The render mode of the debug menu's canvas.
///
///
/// The render mode can either be in world space, screen space or in an automated mode that sets mobile platforms
/// to screen space and non-mobile platforms to worldspace.
///
public DebugMenuRenderMode debugMenuRenderMode
{
get => m_DebugMenuRenderMode;
set => m_DebugMenuRenderMode = value;
}
//Managers
XROrigin m_Origin;
ARSession m_Session;
ARCameraManager m_CameraManager;
//Visuals
GameObject m_OriginAxis;
GameObject m_PlaneVisualizers;
ParticleSystem m_PointCloudVisualizer;
GameObject m_AnchorVisualizers;
//Labels
int m_PreviousFps;
int m_PreviousTrackingMode = -1;
//Dictionaries
Dictionary m_PlaneLineRenderers = new();
Dictionary m_AnchorPrefabs = new();
Dictionary m_Points = new();
ParticleSystem.Particle[] m_Particles;
//Misc
Camera m_CameraAR;
bool m_CameraFollow;
int m_NumParticles;
//Configuration
XRSessionSubsystem m_SessionSubsystem;
bool m_ConfigMenuSetup;
bool m_CameraMenuSetup;
bool m_CanvasRenderModeSetup;
Configuration m_CurrentConfiguration;
XRCameraConfiguration m_CurrentCameraConfiguration;
int m_PreviousConfigCol = -1;
List> m_ConfigurationUI = new();
List m_ColumnLabels = new();
void Start()
{
if(!CheckMenuConfigured())
{
enabled = false;
Debug.LogError($"The menu has not been configured correctly and will currently be disabled. For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu.");
}
else
{
ConfigureMenuPosition();
}
}
bool CheckMenuConfigured()
{
if(m_DisplayInfoMenuButton == null && m_DisplayConfigurationsMenuButton == null && m_DisplayDebugOptionsMenuButton == null && m_DisplayCameraConfigurationsMenuButton == null && m_Toolbar == null)
{
return false;
}
else if(m_DisplayInfoMenuButton == null || m_DisplayConfigurationsMenuButton == null || m_DisplayDebugOptionsMenuButton == null || m_DisplayCameraConfigurationsMenuButton == null || m_ShowOriginButton == null ||
m_ShowPlanesButton == null || m_ShowAnchorsButton == null || m_ShowPointCloudsButton == null || m_FpsLabel == null || m_ConfigurationMenu == null ||
m_TrackingModeLabel == null || m_OriginAxisPrefab == null || m_AnchorPrefab == null || m_LineRendererPrefab == null || m_InfoMenu == null || m_DebugOptionsMenu == null || m_ConfigurationMenuRoot == null ||
m_MenuFont == null || m_Toolbar == null || m_DebugOptionsToastMenu == null || m_PointCloudParticleSystem == null || m_CameraConfigurationMenu == null || m_CameraResolutionLabel == null || m_CameraFrameRateLabel == null ||
m_CameraDepthSensorLabel == null || m_CameraConfigurationDropdown == null)
{
Debug.LogWarning("The menu has not been fully configured so some functionality will be disabled. For an already configured menu, right-click on the Scene Inspector > XR > ARDebugMenu");
}
return true;
}
void OnEnable()
{
InitMenu();
ConfigureButtons();
}
void OnDisable()
{
if(m_Origin != null)
{
var planeManager = m_Origin.GetComponent();
if(planeManager != null)
{
planeManager.trackablesChanged.RemoveListener(OnPlaneChanged);
}
var anchorManager = m_Origin.GetComponent();
if(anchorManager != null)
{
anchorManager.trackablesChanged.RemoveListener(OnAnchorChanged);
}
var pointCloudManager = m_Origin.GetComponent();
if(pointCloudManager != null)
{
pointCloudManager.trackablesChanged.RemoveListener(OnPointCloudChanged);
}
}
DeregisterUIListeners();
}
void Update()
{
int fps = (int)(1.0f / Time.unscaledDeltaTime);
if(fps != m_PreviousFps)
{
m_FpsLabel.text = fps.ToString();
m_PreviousFps = fps;
}
var state = (int)m_Session.currentTrackingMode;
if(state != m_PreviousTrackingMode)
{
m_TrackingModeLabel.text = m_Session.currentTrackingMode.ToString();
m_PreviousTrackingMode = state;
}
if(m_CameraFollow == true)
{
FollowCamera();
}
}
void LateUpdate()
{
if(!m_ConfigMenuSetup)
{
SetupConfigurationMenu();
m_ConfigMenuSetup = true;
}
if(!m_CameraMenuSetup)
{
PopulateCameraDropdown();
}
if(m_SessionSubsystem != null)
{
if(m_SessionSubsystem.currentConfiguration.HasValue && m_SessionSubsystem.currentConfiguration.Value != m_CurrentConfiguration)
{
m_CurrentConfiguration = (Configuration)m_SessionSubsystem.currentConfiguration;
HighlightCurrentConfiguration(m_CurrentConfiguration.descriptor);
}
}
if(m_CameraManager != null)
{
var cameraConfig = m_CameraManager.currentConfiguration;
if(cameraConfig.HasValue && cameraConfig != m_CurrentCameraConfiguration)
{
m_CameraResolutionLabel.text = cameraConfig.Value.resolution != null? cameraConfig.Value.resolution.ToString() : "Not Available";
m_CameraFrameRateLabel.text = cameraConfig.Value.framerate != null? cameraConfig.Value.framerate.ToString() : "Not Available";
m_CameraDepthSensorLabel.text = cameraConfig.Value.depthSensorSupported.ToString();
m_CurrentCameraConfiguration = (XRCameraConfiguration) cameraConfig;
}
}
if (!m_CanvasRenderModeSetup)
{
ConfigureCanvasRenderBehavior();
m_CanvasRenderModeSetup = true;
}
}
void InitMenu()
{
var eventSystem = FindAnyObjectByType();
if(eventSystem == null)
{
Debug.LogError($"Failed to find EventSystem in current scene. As a result, this component will be disabled.");
enabled = false;
return;
}
var session = FindAnyObjectByType();
if(session == null)
{
Debug.LogError($"Failed to find ARSession in current scene. As a result, this component will be disabled.");
enabled = false;
return;
}
m_Session = session;
var origin = FindAnyObjectByType();
if(origin == null)
{
Debug.LogError($"Failed to find XROrigin in current scene. As a result, this component will be disabled.");
enabled = false;
return;
}
m_Origin = origin;
var cameraManager = FindAnyObjectByType();
if(cameraManager == null)
{
Debug.LogWarning($"Failed to find an ARCameraManager in current scene. As a result, the camera configuration menu will be disabled.");
DisableToolbarButton(m_DisplayCameraConfigurationsMenuButton);
}
else
{
m_CameraManager = cameraManager;
}
m_CameraAR = m_Origin.Camera;
#if !UNITY_IOS && !UNITY_ANDROID
if(m_CameraAR == null)
{
Debug.LogError($"Failed to find camera attached to XROrigin. As a result, this component will be disabled.");
enabled = false;
return;
}
#endif
m_DisplayInfoMenuButton.onClick.AddListener(delegate { ShowMenu(m_InfoMenu); });
m_DisplayConfigurationsMenuButton.onClick.AddListener(delegate { ShowMenu(m_ConfigurationMenu); });
m_DisplayDebugOptionsMenuButton.onClick.AddListener(delegate { ShowMenu(m_DebugOptionsMenu); });
m_DisplayCameraConfigurationsMenuButton.onClick.AddListener(delegate { ShowMenu(m_CameraConfigurationMenu); });
m_CameraConfigurationDropdown.onValueChanged.AddListener(delegate { OnCameraDropdownValueChanged(m_CameraConfigurationDropdown); });
}
void ConfigureCanvasRenderBehavior()
{
Canvas menu = GetComponent