// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (c) VRMADA, All rights reserved.
//
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Avatar;
using UltimateXR.Extensions.System;
using UltimateXR.Extensions.Unity.Math;
using UltimateXR.Manipulation;
using UltimateXR.UI.UnityInputModule;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
#endif
namespace UltimateXR.Extensions.Unity
{
///
/// extensions.
///
public static class GameObjectExt
{
#region Public Methods
///
/// Gets the Component of a given type in the GameObject or any of its parents. It also works on prefabs, where regular
/// will not work:
/// https://issuetracker.unity3d.com/issues/getcomponentinparent-is-returning-null-when-the-gameobject-is-a-prefab
/// It also works on objects recently instantiated or on disabled objects.
///
/// Component type to get
/// Component in same GameObject or any of its parents. Null if it wasn't found
public static T SafeGetComponentInParent(this GameObject self)
{
T parent = self.GetComponentInParent();
return parent ?? self.GetComponentsInParent(true).FirstOrDefault();
}
///
/// Activates/deactivates the object if it isn't active already.
///
/// GameObject to activate
/// Whether to activate or deactivate the object
public static void CheckSetActive(this GameObject self, bool activate)
{
if (self.activeSelf != activate)
{
self.SetActive(activate);
}
}
///
/// Gets a unique path in the scene for the given GameObject. It will include sibling indices to make it unique.
///
/// GameObject to get the unique path for
/// Unique GameObject path string
///
public static string GetUniqueScenePath(this GameObject self)
{
self.ThrowIfNull(nameof(self));
return self.transform.GetUniqueScenePath();
}
///
/// Gets the path in the scene of the given GameObject.
///
/// GameObject to get the scene path for
/// Path of the GameObject in the scene
///
public static string GetPathUnderScene(this GameObject self)
{
self.ThrowIfNull(nameof(self));
return self.transform.GetPathUnderScene();
}
///
/// Checks if the given GameObject is the root GameObject inside a prefab.
///
/// GameObject to check
/// Whether the GameObject is the root GameObject inside a prefab
public static bool IsPrefabRoot(this GameObject self)
{
#if UNITY_EDITOR
if (PrefabStageUtility.GetCurrentPrefabStage() != null && PrefabStageUtility.GetCurrentPrefabStage().prefabContentsRoot == self)
{
return true;
}
#endif
return self.scene.name == null && self.transform.parent == null;
}
///
/// Checks if the given GameObject is located inside a prefab.
///
/// GameObject to check
/// Whether the GameObject is located inside a prefab
public static bool IsInPrefab(this GameObject self)
{
#if UNITY_EDITOR
if (PrefabStageUtility.GetCurrentPrefabStage() != null && PrefabStageUtility.GetCurrentPrefabStage().IsPartOfPrefabContents(self))
{
return true;
}
return PrefabUtility.IsPartOfPrefabAsset(self);
#else
return self.scene.name == null;
#endif
}
#if UNITY_EDITOR
///
/// Gets the GUID of the prefab the GameObject is in, if it is in a prefab, or the GUID of the prefab the GameObject
/// was instantiated from, if it was instantiated from a prefab. If it is not
///
/// The GameObject to retrieve the information of
/// If the call was successful, returns the GUID or string.Empty
/// If the call was successful, returns the asset path or string.Empty
/// Whether the call was successful
/// The reason the call can be unsuccessful is because Unity for some reason will report
/// a null/empty asset path even though PrefabUtility.IsPartOfPrefabAsset() returns true.
/// This behaviour happens when in prefab isolation/context mode in the editor
public static bool GetPrefabGuid(this GameObject self, out string prefabGuid, out string assetPath)
{
prefabGuid = string.Empty;
assetPath = string.Empty;
if (PrefabStageUtility.GetCurrentPrefabStage() != null && PrefabStageUtility.GetCurrentPrefabStage().IsPartOfPrefabContents(self))
{
// Asset in prefab view
assetPath = PrefabStageUtility.GetCurrentPrefabStage().assetPath;
}
else if (PrefabUtility.IsPartOfPrefabAsset(self))
{
// Prefab asset
assetPath = AssetDatabase.GetAssetPath(self);
if (string.IsNullOrEmpty(assetPath))
{
return false;
}
}
else if (PrefabUtility.IsPartOfNonAssetPrefabInstance(self) && self.scene.name != null)
{
GameObject instanceRoot = PrefabUtility.GetOutermostPrefabInstanceRoot(self);
GameObject rootPrefab = PrefabUtility.GetCorrespondingObjectFromSource(instanceRoot);
assetPath = AssetDatabase.GetAssetPath(rootPrefab);
}
if (!string.IsNullOrEmpty(assetPath))
{
// Try to get GUID from asset path, checking if path is valid. Otherwise return null.
// Check if all zeroes, which means invalid guid.
prefabGuid = AssetDatabase.GUIDFromAssetPath(assetPath).ToString();
prefabGuid = string.IsNullOrEmpty(prefabGuid) || prefabGuid.All(c => c == '0') ? null : prefabGuid;
return true;
}
return false;
}
#endif
///
/// Checks whether the given GameObject is dynamic. Since doesn't work at runtime
/// due to the static flags being editor-only, a workaround is required to try to find out if an object is dynamic or
/// not.
///
/// GameObject to check
/// Whether the object appears to be dynamic
public static bool IsDynamic(this GameObject self)
{
return self.GetComponentInParent() != null ||
self.GetComponentInParent() != null ||
self.GetComponentInParent() != null ||
self.GetComponentInParent() != null;
}
///
/// Gets the Component of a given type. If it doesn't exist, it is added to the GameObject.
///
/// Target GameObject where the component will be looked for and added to if it doesn't exist
/// Component type to get or add
/// Existing component or newly added if it didn't exist before
public static T GetOrAddComponent(this GameObject self) where T : Component
{
T component = self.GetComponent();
if (component == null)
{
#if UNITY_EDITOR
if (Application.isPlaying)
{
component = self.AddComponent();
}
else
{
component = Undo.AddComponent(self);
}
#else
component = self.AddComponent();
#endif
}
return component;
}
///
/// Creates a new GameObject in the exact same position as the given one and parents it.
///
///
/// GameObject to parent the new object to and also place it at the same position
/// and with the same orientation
///
/// Name for the new GameObject
/// New created GameObject
public static GameObject CreateGameObjectAndParentSameTransform(GameObject parent, string newGameObjectName)
{
GameObject newGameObject = new GameObject(newGameObjectName);
newGameObject.transform.parent = parent.transform;
newGameObject.transform.localPosition = Vector3.zero;
newGameObject.transform.localRotation = Quaternion.identity;
return newGameObject;
}
///
/// Computes the geometric center of the given GameObject based on all the MeshRenderers in the hierarchy.
///
/// GameObject to compute the geometric center of
/// Geometric center
public static Vector3 GetGeometricCenter(this GameObject self)
{
IEnumerable meshRenderers = self.GetComponentsInChildren().Where(r => !r.hideFlags.HasFlag(HideFlags.HideInHierarchy) && r.enabled);
bool initialized = false;
Vector3 min = Vector3.zero;
Vector3 max = Vector3.zero;
if (!meshRenderers.Any())
{
return self.transform.position;
}
foreach (MeshRenderer renderer in meshRenderers)
{
if (!initialized)
{
initialized = true;
min = renderer.bounds.min;
max = renderer.bounds.max;
}
else
{
min = Vector3.Min(min, renderer.bounds.min);
max = Vector3.Max(max, renderer.bounds.max);
}
}
return (min + max) * 0.5f;
}
///
/// Calculates the . The bounds are the 's bounds
/// if there is one in the GameObject. Otherwise it will encapsulate all renderers found in the children.
/// If is true, it will also encapsulate all renderers found in
/// the children no matter if the GameObject has a Renderer component or not.
///
/// The GameObject whose to get
///
/// Whether to also encapsulate all renderers found in the children no matter if the
/// GameObject has a Renderer component or not
///
///
/// in world-space.
///
public static Bounds GetBounds(this GameObject self, bool forceRecurseIntoChildren)
{
Renderer renderer = self.GetComponent();
if (renderer != null && !forceRecurseIntoChildren)
{
if (renderer.enabled)
{
return renderer.bounds;
}
return new Bounds(self.transform.position, Vector3.zero);
}
IEnumerable renderers = self.GetComponentsInChildren().Where(r => !r.hideFlags.HasFlag(HideFlags.HideInHierarchy) && r.enabled);
if (!renderers.Any())
{
return new Bounds(self.transform.position, Vector3.zero);
}
Vector3 min = Vector3Ext.Min(renderers.Select(r => r.bounds.min));
Vector3 max = Vector3Ext.Max(renderers.Select(r => r.bounds.max));
return new Bounds((max + min) * 0.5f, max - min);
}
///
/// Calculates the in local space. The bounds are the
/// 's bounds if there is one in the GameObject. Otherwise it will encapsulate all renderers
/// found in the children.
/// If is true, it will also encapsulate all renderers found in
/// the children no matter if the GameObject has a Renderer component or not.
///
/// The GameObject whose local to get
///
/// Whether to also encapsulate all renderers found in the children no matter if the
/// GameObject has a Renderer component or not
///
///
/// Local .
///
public static Bounds GetLocalBounds(this GameObject self, bool forceRecurseIntoChildren)
{
Renderer renderer = self.GetComponent();
if (renderer != null && !forceRecurseIntoChildren)
{
if (renderer.enabled)
{
return renderer.localBounds;
}
return new Bounds();
}
IEnumerable renderers = self.GetComponentsInChildren().Where(r => !r.hideFlags.HasFlag(HideFlags.HideInHierarchy) && r.enabled);
if (!renderers.Any())
{
return new Bounds();
}
IEnumerable allMinMaxToLocal = renderers.Select(r => self.transform.InverseTransformPoint(r.transform.TransformPoint(r.localBounds.min))).Concat(renderers.Select(r => self.transform.InverseTransformPoint(r.transform.TransformPoint(r.localBounds.max))));
Vector3 min = Vector3Ext.Min(allMinMaxToLocal);
Vector3 max = Vector3Ext.Max(allMinMaxToLocal);
return new Bounds((max + min) * 0.5f, max - min);
}
///
/// Sets the layer of a GameObject and all its children.
///
/// The root GameObject from where to start
/// The layer value to assign
public static void SetLayerRecursively(this GameObject self, int layer)
{
if (self != null)
{
Transform selfTransform = self.transform;
self.gameObject.layer = layer;
for (int i = 0; i < selfTransform.childCount; ++i)
{
SetLayerRecursively(selfTransform.GetChild(i).gameObject, layer);
}
}
}
///
/// Checks whether the given GameObject's layer is present in a layer mask.
///
/// The GameObject whose layer to check
/// The layer mask to check against
/// Whether the GameObject's layer is present in the layer mask
public static bool IsInLayerMask(this GameObject self, LayerMask layerMask)
{
return (1 << self.layer & layerMask.value) != 0;
}
///
/// Gets the topmost upwards in the hierarchy if it exists.
///
/// The GameObject whose parents to look for
/// The topmost component upwards in the hierarchy or null if it doesn't exists
public static UxrCanvas GetTopmostCanvas(this GameObject self)
{
UxrCanvas[] canvases = self.GetComponentsInParent();
return ComponentExt.GetCommonRootComponentFromSet(canvases);
}
#endregion
}
}