// Control|Controls3D|100010 namespace VRTK { using UnityEngine; /// /// Event Payload /// /// The current value being reported by the control. /// The normalized value being reported by the control. public struct Control3DEventArgs { public float value; public float normalizedValue; } /// /// Event Payload /// /// this object /// public delegate void Control3DEventHandler(object sender, Control3DEventArgs e); /// /// All 3D controls extend the `VRTK_Control` abstract class which provides a default set of methods and events that all of the subsequent controls expose. /// [ExecuteInEditMode] [System.Obsolete("`VRTK_Control` has been deprecated. This script will be removed in a future version of VRTK.")] public abstract class VRTK_Control : MonoBehaviour { /// /// The ControlValueRange struct provides a way for each inherited control to support value normalization. /// public struct ControlValueRange { public float controlMin; public float controlMax; } /// /// 3D Control Directions /// public enum Direction { /// /// Attempt to auto detect the axis. /// autodetect, /// /// The world x direction. /// x, /// /// The world y direction. /// y, /// /// The world z direction. /// z } [Tooltip("If active the control will react to the controller without the need to push the grab button.")] public bool interactWithoutGrab = false; /// /// Emitted when the 3D Control value has changed. /// public event Control3DEventHandler ValueChanged; abstract protected void InitRequiredComponents(); abstract protected bool DetectSetup(); abstract protected ControlValueRange RegisterValueRange(); protected Bounds bounds; protected bool setupSuccessful = true; protected VRTK_ControllerRigidbodyActivator autoTriggerVolume; protected float value; protected static Color COLOR_OK = Color.yellow; protected static Color COLOR_ERROR = new Color(1, 0, 0, 0.9f); protected const float MIN_OPENING_DISTANCE = 20f; // percentage how far open something needs to be in order to activate it protected ControlValueRange valueRange; protected GameObject controlContent; protected bool hideControlContent = false; public virtual void OnValueChanged(Control3DEventArgs e) { if (ValueChanged != null) { ValueChanged(this, e); } } /// /// The GetValue method returns the current value/position/setting of the control depending on the control that is extending this abstract class. /// /// The current value of the control. public virtual float GetValue() { return value; } /// /// The GetNormalizedValue method returns the current value mapped onto a range between 0 and 100. /// /// The current normalized value of the control. public virtual float GetNormalizedValue() { return Mathf.Abs(Mathf.Round((value - valueRange.controlMin) / (valueRange.controlMax - valueRange.controlMin) * 100)); } /// /// The SetContent method sets the given game object as the content of the control. This will then disable and optionally hide the content when a control is obscuring its view to prevent interacting with content within a control. /// /// The content to be considered within the control. /// When true the content will be hidden in addition to being non-interactable in case the control is fully closed. public virtual void SetContent(GameObject content, bool hideContent) { controlContent = content; hideControlContent = hideContent; } /// /// The GetContent method returns the current game object of the control's content. /// /// The currently stored content for the control. public virtual GameObject GetContent() { return controlContent; } abstract protected void HandleUpdate(); protected virtual void Awake() { if (Application.isPlaying) { InitRequiredComponents(); if (interactWithoutGrab) { CreateTriggerVolume(); } } setupSuccessful = DetectSetup(); if (Application.isPlaying) { valueRange = RegisterValueRange(); HandleInteractables(); } } protected virtual void Update() { if (!Application.isPlaying) { setupSuccessful = DetectSetup(); } else if (setupSuccessful) { float oldValue = value; HandleUpdate(); // trigger events if (value != oldValue) { HandleInteractables(); OnValueChanged(SetControlEvent()); } } } protected virtual Control3DEventArgs SetControlEvent() { Control3DEventArgs e; e.value = GetValue(); e.normalizedValue = GetNormalizedValue(); return e; } protected virtual void OnDrawGizmos() { if (!enabled) { return; } bounds = VRTK_SharedMethods.GetBounds(transform); Gizmos.color = (setupSuccessful) ? COLOR_OK : COLOR_ERROR; if (setupSuccessful) { Gizmos.DrawWireCube(bounds.center, bounds.size); } else { Gizmos.DrawCube(bounds.center, bounds.size * 1.01f); // draw slightly bigger to eliminate flickering } } protected virtual void CreateTriggerVolume() { GameObject autoTriggerVolumeGO = new GameObject(name + "-Trigger"); autoTriggerVolumeGO.transform.SetParent(transform); autoTriggerVolume = autoTriggerVolumeGO.AddComponent(); // calculate bounding box Bounds triggerBounds = VRTK_SharedMethods.GetBounds(transform); triggerBounds.Expand(triggerBounds.size * 0.2f); autoTriggerVolumeGO.transform.position = triggerBounds.center; BoxCollider triggerCollider = autoTriggerVolumeGO.AddComponent(); triggerCollider.isTrigger = true; triggerCollider.size = triggerBounds.size; } protected Vector3 GetThirdDirection(Vector3 axis1, Vector3 axis2) { bool xTaken = axis1.x != 0 || axis2.x != 0; bool yTaken = axis1.y != 0 || axis2.y != 0; bool zTaken = axis1.z != 0 || axis2.z != 0; if (xTaken && yTaken) { return Vector3.forward; } else if (xTaken && zTaken) { return Vector3.up; } else { return Vector3.right; } } protected virtual void HandleInteractables() { if (controlContent == null) { return; } if (hideControlContent) { controlContent.SetActive(value > 0); } // do not cache objects since otherwise they would still be made inactive once taken out of the content VRTK_InteractableObject[] foundInteractableObjects = controlContent.GetComponentsInChildren(true); for (int i = 0; i < foundInteractableObjects.Length; i++) { foundInteractableObjects[i].enabled = value > MIN_OPENING_DISTANCE; } } } }