// Shared Methods|Utilities|90030
namespace VRTK
{
using UnityEngine;
using UnityEngine.SceneManagement;
#if UNITY_EDITOR
using UnityEditor;
#endif
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine.VR;
///
/// The Shared Methods script is a collection of reusable static methods that are used across a range of different scripts.
///
public static class VRTK_SharedMethods
{
///
/// The GetBounds methods returns the bounds of the transform including all children in world space.
///
///
/// Resets the rotation of the transform temporarily to 0 to eliminate skewed bounds.
/// Does not consider the stated object when calculating the bounds.
/// The bounds of the transform.
public static Bounds GetBounds(Transform transform, Transform excludeRotation = null, Transform excludeTransform = null)
{
Quaternion oldRotation = Quaternion.identity;
if (excludeRotation)
{
oldRotation = excludeRotation.rotation;
excludeRotation.rotation = Quaternion.identity;
}
bool boundsInitialized = false;
Bounds bounds = new Bounds(transform.position, Vector3.zero);
Renderer[] renderers = transform.GetComponentsInChildren();
foreach (Renderer renderer in renderers)
{
if (excludeTransform != null && renderer.transform.IsChildOf(excludeTransform))
{
continue;
}
// do late initialization in case initial transform does not contain any renderers
if (!boundsInitialized)
{
bounds = new Bounds(renderer.transform.position, Vector3.zero);
boundsInitialized = true;
}
bounds.Encapsulate(renderer.bounds);
}
if (bounds.size.magnitude == 0)
{
// do second pass as there were no renderers, this time with colliders
BoxCollider[] colliders = transform.GetComponentsInChildren();
foreach (BoxCollider collider in colliders)
{
if (excludeTransform != null && collider.transform.IsChildOf(excludeTransform))
{
continue;
}
// do late initialization in case initial transform does not contain any colliders
if (!boundsInitialized)
{
bounds = new Bounds(collider.transform.position, Vector3.zero);
boundsInitialized = true;
}
bounds.Encapsulate(collider.bounds);
}
}
if (excludeRotation)
{
excludeRotation.rotation = oldRotation;
}
return bounds;
}
///
/// The IsLowest method checks to see if the given value is the lowest number in the given array of values.
///
/// The value to check to see if it is lowest.
/// The array of values to check against.
/// Returns true if the value is lower than all numbers in the given array, returns false if it is not the lowest.
public static bool IsLowest(float value, float[] others)
{
foreach (float o in others)
{
if (o <= value)
{
return false;
}
}
return true;
}
///
/// The AddCameraFade method finds the headset camera and adds a headset fade script to it.
///
/// The transform of the headset camera.
public static Transform AddCameraFade()
{
var camera = VRTK_DeviceFinder.HeadsetCamera();
VRTK_SDK_Bridge.AddHeadsetFade(camera);
return camera;
}
///
/// The CreateColliders method attempts to add box colliders to all child objects in the given object that have a renderer but no collider.
///
/// The game object to attempt to add the colliders to.
public static void CreateColliders(GameObject obj)
{
Renderer[] renderers = obj.GetComponentsInChildren();
foreach (Renderer renderer in renderers)
{
if (!renderer.gameObject.GetComponent())
{
renderer.gameObject.AddComponent();
}
}
}
///
/// The CloneComponent method takes a source component and copies it to the given destination game object.
///
/// The component to copy.
/// The game object to copy the component to.
/// Determines whether the properties of the component as well as the fields should be copied.
/// The component that has been cloned onto the given game object.
public static Component CloneComponent(Component source, GameObject destination, bool copyProperties = false)
{
Component tmpComponent = destination.gameObject.AddComponent(source.GetType());
if (copyProperties)
{
foreach (PropertyInfo p in source.GetType().GetProperties())
{
if (p.CanWrite)
{
p.SetValue(tmpComponent, p.GetValue(source, null), null);
}
}
}
foreach (FieldInfo f in source.GetType().GetFields())
{
f.SetValue(tmpComponent, f.GetValue(source));
}
return tmpComponent;
}
///
/// The ColorDarken method takes a given colour and darkens it by the given percentage.
///
/// The source colour to apply the darken to.
/// The percent to darken the colour by.
/// The new colour with the darken applied.
public static Color ColorDarken(Color color, float percent)
{
return new Color(NumberPercent(color.r, percent), NumberPercent(color.g, percent), NumberPercent(color.b, percent), color.a);
}
///
/// The RoundFloat method is used to round a given float to the given decimal places.
///
/// The float to round.
/// The number of decimal places to round to.
/// If this is true then the decimal places must be given in the decimal multiplier, e.g. 10 for 1dp, 100 for 2dp, etc.
/// The rounded float.
public static float RoundFloat(float givenFloat, int decimalPlaces, bool rawFidelity = false)
{
float roundBy = (rawFidelity ? decimalPlaces : Mathf.Pow(10.0f, decimalPlaces));
return Mathf.Round(givenFloat * roundBy) / roundBy;
}
///
/// The IsEditTime method determines if the state of Unity is in the Unity Editor and the scene is not in play mode.
///
/// Returns true if Unity is in the Unity Editor and not in play mode.
public static bool IsEditTime()
{
#if UNITY_EDITOR
return !EditorApplication.isPlayingOrWillChangePlaymode;
#else
return false;
#endif
}
///
/// The TriggerHapticPulse/1 method calls a single haptic pulse call on the controller for a single tick.
///
/// The controller index to activate the haptic feedback on.
/// The intensity of the rumble of the controller motor. `0` to `1`.
[Obsolete("`VRTK_SharedMethods.TriggerHapticPulse(controllerIndex, strength)` has been replaced with `VRTK_ControllerHaptics.TriggerHapticPulse(controllerReference, strength)`. This method will be removed in a future version of VRTK.")]
public static void TriggerHapticPulse(uint controllerIndex, float strength)
{
VRTK_ControllerHaptics.TriggerHapticPulse(VRTK_ControllerReference.GetControllerReference(controllerIndex), strength);
}
///
/// The TriggerHapticPulse/3 method calls a haptic pulse for a specified amount of time rather than just a single tick. Each pulse can be separated by providing a `pulseInterval` to pause between each haptic pulse.
///
/// The controller index to activate the haptic feedback on.
/// The intensity of the rumble of the controller motor. `0` to `1`.
/// The length of time the rumble should continue for.
/// The interval to wait between each haptic pulse.
[Obsolete("`VRTK_SharedMethods.TriggerHapticPulse(controllerIndex, strength, duration, pulseInterval)` has been replaced with `VRTK_ControllerHaptics.TriggerHapticPulse(controllerReference, strength, duration, pulseInterval)`. This method will be removed in a future version of VRTK.")]
public static void TriggerHapticPulse(uint controllerIndex, float strength, float duration, float pulseInterval)
{
VRTK_ControllerHaptics.TriggerHapticPulse(VRTK_ControllerReference.GetControllerReference(controllerIndex), strength, duration, pulseInterval);
}
///
/// The CancelHapticPulse method cancels the existing running haptic pulse on the given controller index.
///
/// The controller index to cancel the haptic feedback on.
[Obsolete("`VRTK_SharedMethods.CancelHapticPulse(controllerIndex)` has been replaced with `VRTK_SharedMethods.CancelHapticPulse(controllerReference)`. This method will be removed in a future version of VRTK.")]
public static void CancelHapticPulse(uint controllerIndex)
{
VRTK_ControllerHaptics.CancelHapticPulse(VRTK_ControllerReference.GetControllerReference(controllerIndex));
}
///
/// The SetOpacity method allows the opacity of the given GameObject to be changed. A lower alpha value will make the object more transparent, such as `0.5f` will make the controller partially transparent where as `0f` will make the controller completely transparent.
///
/// The GameObject to change the renderer opacity on.
/// The alpha level to apply to opacity of the controller object. `0f` to `1f`.
/// The time to transition from the current opacity to the new opacity.
[Obsolete("`VRTK_SharedMethods.SetOpacity(model, alpha, transitionDuration)` has been replaced with `VRTK_ObjectAppearance.SetOpacity(model, alpha, transitionDuration)`. This method will be removed in a future version of VRTK.")]
public static void SetOpacity(GameObject model, float alpha, float transitionDuration = 0f)
{
VRTK_ObjectAppearance.SetOpacity(model, alpha, transitionDuration);
}
///
/// The SetRendererVisible method turns on renderers of a given GameObject. It can also be provided with an optional model to ignore the render toggle on.
///
/// The GameObject to show the renderers for.
/// An optional GameObject to ignore the renderer toggle on.
[Obsolete("`VRTK_SharedMethods.SetRendererVisible(model, ignoredModel)` has been replaced with `VRTK_ObjectAppearance.SetRendererVisible(model, ignoredModel)`. This method will be removed in a future version of VRTK.")]
public static void SetRendererVisible(GameObject model, GameObject ignoredModel = null)
{
VRTK_ObjectAppearance.SetRendererVisible(model, ignoredModel);
}
///
/// The SetRendererHidden method turns off renderers of a given GameObject. It can also be provided with an optional model to ignore the render toggle on.
///
/// The GameObject to hide the renderers for.
/// An optional GameObject to ignore the renderer toggle on.
[Obsolete("`VRTK_SharedMethods.SetRendererHidden(model, ignoredModel)` has been replaced with `VRTK_ObjectAppearance.SetRendererHidden(model, ignoredModel)`. This method will be removed in a future version of VRTK.")]
public static void SetRendererHidden(GameObject model, GameObject ignoredModel = null)
{
VRTK_ObjectAppearance.SetRendererHidden(model, ignoredModel);
}
///
/// The ToggleRenderer method turns on or off the renderers of a given GameObject. It can also be provided with an optional model to ignore the render toggle of.
///
/// If true then the renderers will be enabled, if false the renderers will be disabled.
/// The GameObject to toggle the renderer states of.
/// An optional GameObject to ignore the renderer toggle on.
[Obsolete("`VRTK_SharedMethods.ToggleRenderer(state, model, ignoredModel)` has been replaced with `VRTK_ObjectAppearance.ToggleRenderer(state, model, ignoredModel)`. This method will be removed in a future version of VRTK.")]
public static void ToggleRenderer(bool state, GameObject model, GameObject ignoredModel = null)
{
VRTK_ObjectAppearance.ToggleRenderer(state, model, ignoredModel);
}
///
/// The HighlightObject method calls the Highlight method on the highlighter attached to the given GameObject with the provided colour.
///
/// The GameObject to attempt to call the Highlight on.
/// The colour to highlight to.
/// The duration in time to fade from the initial colour to the target colour.
[Obsolete("`VRTK_SharedMethods.HighlightObject(model, highlightColor, fadeDuration)` has been replaced with `VRTK_ObjectAppearance.HighlightObject(model, highlightColor, fadeDuration)`. This method will be removed in a future version of VRTK.")]
public static void HighlightObject(GameObject model, Color? highlightColor, float fadeDuration = 0f)
{
VRTK_ObjectAppearance.HighlightObject(model, highlightColor, fadeDuration);
}
///
/// The UnhighlightObject method calls the Unhighlight method on the highlighter attached to the given GameObject.
///
/// The GameObject to attempt to call the Unhighlight on.
[Obsolete("`VRTK_SharedMethods.UnhighlightObject(model)` has been replaced with `VRTK_ObjectAppearance.UnhighlightObject(model)`. This method will be removed in a future version of VRTK.")]
public static void UnhighlightObject(GameObject model)
{
VRTK_ObjectAppearance.UnhighlightObject(model);
}
///
/// The Mod method is used to find the remainder of the sum a/b.
///
/// The dividend value.
/// The divisor value.
/// The remainder value.
public static float Mod(float a, float b)
{
return a - b * Mathf.Floor(a / b);
}
///
/// Finds the first with a given name and an ancestor that has a specific component.
///
///
/// This method returns active as well as inactive s in the scene. It doesn't return assets.
/// For performance reasons it is recommended to not use this function every frame. Cache the result in a member variable at startup instead.
///
/// The component type that needs to be on an ancestor of the wanted . Must be a subclass of .
/// The name of the wanted . If it contains a '/' character, this method traverses the hierarchy like a path name, beginning on the game object that has a component of type .
/// The with name and an ancestor that has a . If no such is found is returned.
public static GameObject FindEvenInactiveGameObject(string gameObjectName = null) where T : Component
{
if (string.IsNullOrEmpty(gameObjectName))
{
T foundComponent = FindEvenInactiveComponent();
return foundComponent == null ? null : foundComponent.gameObject;
}
Scene activeScene = SceneManager.GetActiveScene();
IEnumerable gameObjects = Resources.FindObjectsOfTypeAll()
.Select(component => component.gameObject)
.Where(gameObject => gameObject.scene == activeScene);
#if UNITY_EDITOR
gameObjects = gameObjects.Where(gameObject => !AssetDatabase.Contains(gameObject));
#endif
return gameObjects.Select(gameObject =>
{
Transform transform = gameObject.transform.Find(gameObjectName);
return transform == null ? null : transform.gameObject;
})
.FirstOrDefault(gameObject => gameObject != null);
}
///
/// Finds all components of a given type.
///
///
/// This method returns components from active as well as inactive s in the scene. It doesn't return assets.
/// For performance reasons it is recommended to not use this function every frame. Cache the result in a member variable at startup instead.
///
/// The component type to search for. Must be a subclass of .
/// All the found components. If no component is found an empty array is returned.
public static T[] FindEvenInactiveComponents() where T : Component
{
Scene activeScene = SceneManager.GetActiveScene();
return Resources.FindObjectsOfTypeAll()
.Where(@object => @object.gameObject.scene == activeScene)
#if UNITY_EDITOR
.Where(@object => !AssetDatabase.Contains(@object))
#endif
.ToArray();
}
///
/// Finds the first component of a given type.
///
///
/// This method returns components from active as well as inactive s in the scene. It doesn't return assets.
/// For performance reasons it is recommended to not use this function every frame. Cache the result in a member variable at startup instead.
///
/// The component type to search for. Must be a subclass of .
/// The found component. If no component is found is returned.
public static T FindEvenInactiveComponent() where T : Component
{
Scene activeScene = SceneManager.GetActiveScene();
return Resources.FindObjectsOfTypeAll()
.Where(@object => @object.gameObject.scene == activeScene)
#if UNITY_EDITOR
.FirstOrDefault(@object => !AssetDatabase.Contains(@object));
#else
.FirstOrDefault();
#endif
}
///
/// The GenerateVRTKObjectName method is used to create a standard name string for any VRTK generated object.
///
/// An additiona [AUTOGEN] prefix will be added if this is true.
/// A collection of parameters to add to the generated name.
/// The generated name string.
public static string GenerateVRTKObjectName(bool autoGen, params object[] replacements)
{
string toFormat = "[VRTK]";
if (autoGen)
{
toFormat += "[AUTOGEN]";
}
for (int i = 0; i < replacements.Length; i++)
{
toFormat += "[{" + i + "}]";
}
return string.Format(toFormat, replacements);
}
///
/// The GetGPUTimeLastFrame retrieves the time spent by the GPU last frame, in seconds, as reported by the VR SDK.
///
/// The total GPU time utilized last frame as measured by the VR subsystem.
public static float GetGPUTimeLastFrame()
{
#if UNITY_5_6_OR_NEWER
float gpuTimeLastFrame;
if (UnityEngine.XR.XRStats.TryGetGPUTimeLastFrame(out gpuTimeLastFrame))
{
return gpuTimeLastFrame;
}
return 0f;
#else
return VRStats.gpuTimeLastFrame;
#endif
}
///
/// The Vector2ShallowCompare method compares two given Vector2 objects based on the given fidelity, which is the equivalent of comparing rounded Vector2 elements to determine if the Vector2 elements are equal.
///
/// The Vector2 to compare against.
/// The Vector2 to compare with
/// The number of decimal places to use when doing the comparison on the float elements within the Vector2.
/// Returns true if the given Vector2 objects match based on the given fidelity.
public static bool Vector2ShallowCompare(Vector2 vectorA, Vector2 vectorB, int compareFidelity)
{
var distanceVector = vectorA - vectorB;
return (Math.Round(Mathf.Abs(distanceVector.x), compareFidelity, MidpointRounding.AwayFromZero) < float.Epsilon &&
Math.Round(Mathf.Abs(distanceVector.y), compareFidelity, MidpointRounding.AwayFromZero) < float.Epsilon);
}
///
/// The NumberPercent method is used to determine the percentage of a given value.
///
/// The value to determine the percentage from
/// The percentage to find within the given value.
/// A float containing the percentage value based on the given input.
public static float NumberPercent(float value, float percent)
{
percent = Mathf.Clamp(percent, 0f, 100f);
return (percent == 0f ? value : (value - (percent / 100f)));
}
///
/// The SetGlobalScale method is used to set a transform scale based on a global scale instead of a local scale.
///
/// The reference to the transform to scale.
/// A Vector3 of a global scale to apply to the given transform.
public static void SetGlobalScale(this Transform transform, Vector3 globalScale)
{
transform.localScale = Vector3.one;
transform.localScale = new Vector3(globalScale.x / transform.lossyScale.x, globalScale.y / transform.lossyScale.y, globalScale.z / transform.lossyScale.z);
}
///
/// The GetTypeUnknownAssembly method is used to find a Type without knowing the exact assembly it is in.
///
/// The name of the type to get.
/// The Type, or null if none is found.
public static Type GetTypeUnknownAssembly(string typeName)
{
Type type = Type.GetType(typeName);
if (type != null)
{
return type;
}
foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
{
type = a.GetType(typeName);
if (type != null)
{
return type;
}
}
return null;
}
#if UNITY_EDITOR
public static BuildTargetGroup[] GetValidBuildTargetGroups()
{
return Enum.GetValues(typeof(BuildTargetGroup)).Cast().Where(group =>
{
if (group == BuildTargetGroup.Unknown)
{
return false;
}
string targetGroupName = Enum.GetName(typeof(BuildTargetGroup), group);
FieldInfo targetGroupFieldInfo = typeof(BuildTargetGroup).GetField(targetGroupName, BindingFlags.Public | BindingFlags.Static);
return targetGroupFieldInfo != null && targetGroupFieldInfo.GetCustomAttributes(typeof(ObsoleteAttribute), false).Length == 0;
}).ToArray();
}
#endif
}
}