// 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 } }