using System; using System.Collections; using System.Linq; using Unity.XR.CoreUtils; using UnityEngine; using UnityEngine.XR; using UnityEngine.XR.Management; using static UnityEngine.XR.XRDisplaySubsystem; namespace Unity.XR.XREAL { public static class XREALUtility { public const string k_Identifier = "com.xreal.xr"; private static Camera mainCamera; /// /// Gets the main camera, which is either the XR Origin's camera or null if not found. /// public static Camera MainCamera { get { if (mainCamera == null) { XROrigin origin = FindAnyObjectByType(); mainCamera = (origin != null) ? origin.Camera : null; } return mainCamera; } } /// /// Finds an object of the specified type in the scene. /// /// The type of the object to find. /// The object if found; otherwise, null. public static T FindAnyObjectByType() where T : UnityEngine.Object { #if UNITY_2023_1_OR_NEWER return UnityEngine.Object.FindAnyObjectByType(); #else return UnityEngine.Object.FindObjectOfType(); #endif } /// /// Gets the currently active XR loader. /// /// The active XR loader, or null if no loader is active. public static XRLoader GetActiveLoader() { if (XRGeneralSettings.Instance != null && XRGeneralSettings.Instance.Manager != null) { return XRGeneralSettings.Instance.Manager.activeLoader; } return null; } /// /// Determines whether the XREAL XR loader is currently active. /// /// True if the XREAL XR loader is active; otherwise, false. public static bool IsLoaderActive() { #if UNITY_EDITOR var targetGroup = UnityEditor.BuildPipeline.GetBuildTargetGroup(UnityEditor.EditorUserBuildSettings.activeBuildTarget); var settings = UnityEditor.XR.Management.XRGeneralSettingsPerBuildTarget.XRGeneralSettingsForBuildTarget(targetGroup); return settings != null && settings.Manager.activeLoaders.OfType().Any(); #else return GetActiveLoader() is XREALXRLoader; #endif } /// /// Gets the loaded subsystem of the specified type from the active loader. /// /// The type of the subsystem to retrieve. /// The loaded subsystem of the specified type, or null if not found. public static T GetLoadedSubsystem() where T : class, ISubsystem { XRLoader loader = GetActiveLoader(); if (loader != null) { return loader.GetLoadedSubsystem(); } return null; } /// /// Retrieves the XR render parameter for the specified eye. /// /// The eye index (0 for left, 1 for right). /// The render parameter for the specified eye. /// True if the render parameter is successfully retrieved; otherwise, false. public static bool GetXRRenderParameter(int eye, out XRRenderParameter parameter) { parameter = new XRRenderParameter(); XRDisplaySubsystem displaySubsystem = GetLoadedSubsystem(); if (displaySubsystem == null) return false; var passCount = displaySubsystem.GetRenderPassCount(); if (passCount == 0) return false; int passIndex = passCount == 2 && eye == 1 ? 1 : 0; displaySubsystem.GetRenderPass(passIndex, out XRRenderPass renderPass); var parameterCount = renderPass.GetRenderParameterCount(); if (parameterCount == 0) return false; int parameterIndex = parameterCount == 2 && eye == 1 ? 1 : 0; renderPass.GetRenderParameter(MainCamera, parameterIndex, out parameter); return true; } /// /// Determines if the specified transform is a child of the main camera. /// /// The transform to check. /// True if the transform is a child of the main camera; otherwise, false. public static bool IsChildOfCamera(this Transform transform) { Transform cameraTransform = MainCamera.transform; Transform current = transform; while (current != null) { if (current == cameraTransform) { return true; } current = current.parent; } return false; } /// /// Calculates the relative pose of the specified transform to the main camera. /// /// The transform to calculate the relative pose for. /// The relative pose to the main camera. public static Pose GetRelativePoseToCamera(this Transform transform) { Transform cameraTransform = MainCamera.transform; Vector3 relativePosition = cameraTransform.InverseTransformPoint(transform.position); Quaternion relativeRotation = Quaternion.Inverse(cameraTransform.rotation) * transform.rotation; return new Pose(relativePosition, relativeRotation); } /// /// Calculates the relative pose of the specified transform to the parent of the main camera. /// /// The transform to calculate the relative pose for. /// The relative pose to the parent of the main camera. public static Pose GetRelativePoseToCameraParent(this Transform transform) { Transform cameraTransform = MainCamera.transform; if (cameraTransform.parent != null) { Vector3 relativePosition = cameraTransform.parent.InverseTransformPoint(transform.position); Quaternion relativeRotation = Quaternion.Inverse(cameraTransform.parent.rotation) * transform.rotation; return new Pose(relativePosition, relativeRotation); } else { return new Pose(transform.position, transform.rotation); } } /// /// Adds or retrieves the specified component from the GameObject. /// /// The type of the component to add or retrieve. /// The GameObject to add or retrieve the component from. /// The component of type T. public static T AddOrGetComponent(this GameObject gameObject) where T : Component { if (gameObject.TryGetComponent(out T component)) { return component; } else { return gameObject.AddComponent(); } } /// /// Adds or retrieves the specified component from the Component's GameObject. /// /// The type of the component to add or retrieve. /// The Component to add or retrieve the component from. /// The component of type T. public static T AddOrGetComponent(this Component component) where T : Component { return component.gameObject.AddOrGetComponent(); } /// /// Starts a coroutine that continues until the specified predicate evaluates to true or the timeout expires. /// /// The MonoBehaviour that starts the coroutine. /// The condition to evaluate in the coroutine. /// The maximum time to wait before exiting the coroutine. /// The Coroutine instance. public static Coroutine StartCoroutineUntil(this MonoBehaviour monoBehaviour, Func predicate, float timeoutSeconds) { return monoBehaviour.StartCoroutine(WaitUntilCondition(predicate, timeoutSeconds)); } /// /// Waits until the specified condition is met or the timeout expires. /// /// The condition to evaluate in the coroutine. /// The maximum time to wait before exiting the coroutine. /// Null when the condition is met or timeout expires. private static IEnumerator WaitUntilCondition(Func predicate, float timeoutSeconds) { float startTime = Time.time; while (!predicate()) { if (Time.time - startTime > timeoutSeconds) { yield break; } yield return null; } } #if UNITY_ANDROID static AndroidJavaObject s_UnityActivity; /// /// Gets the current Unity activity in the Android environment. /// public static AndroidJavaObject UnityActivity { get { if (s_UnityActivity == null) { using AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); s_UnityActivity = unityPlayer.GetStatic("currentActivity"); } return s_UnityActivity; } } /// /// Runs the specified action on the Unity Android UI thread. /// /// The action to execute on the UI thread. public static void RunOnUiThread(Action action) { UnityActivity.Call("runOnUiThread", new AndroidJavaRunnable(action)); } #endif } }