// Pointer Direction Indicator|Prefabs|0100 namespace VRTK { using UnityEngine; /// /// Event Payload /// /// this object public delegate void PointerDirectionIndicatorEventHandler(object sender); /// /// Adds a Pointer Direction Indicator to a pointer renderer and determines a given world rotation that can be used by a Destiantion Marker. /// /// /// **Prefab Usage:** /// * Place the `VRTK/Prefabs/PointerDirectionIndicator/PointerDirectionIndicator` prefab into the scene hierarchy. /// * Attach the `PointerDirectionIndicator` scene GameObejct to the `Direction Indicator` inspector parameter on a `VRTK_BasePointerRenderer` component. /// /// > This can be useful for rotating the play area upon teleporting to face the user in a new direction without expecting them to physically turn in the play space. /// public class VRTK_PointerDirectionIndicator : MonoBehaviour { /// /// States of Direction Indicator Visibility. /// public enum VisibilityState { /// /// Only shows the direction indicator when the pointer is active. /// OnWhenPointerActive, /// /// Only shows the direction indicator when the pointer cursor is visible or if the cursor is hidden and the pointer is active. /// AlwaysOnWithPointerCursor } [Header("Control Settings")] [Tooltip("The touchpad axis needs to be above this deadzone for it to register as a valid touchpad angle.")] public Vector2 touchpadDeadzone = Vector2.zero; [Tooltip("The axis to use for the direction coordinates.")] public VRTK_ControllerEvents.Vector2AxisAlias coordinateAxis = VRTK_ControllerEvents.Vector2AxisAlias.Touchpad; [Header("Appearance Settings")] [Tooltip("If this is checked then the reported rotation will include the offset of the headset rotation in relation to the play area.")] public bool includeHeadsetOffset = true; [Tooltip("If this is checked then the direction indicator will be displayed when the location is invalid.")] public bool displayOnInvalidLocation = true; [Tooltip("If this is checked then the pointer valid/invalid colours will also be used to change the colour of the direction indicator.")] public bool usePointerColor = false; [Tooltip("Determines when the direction indicator will be visible.")] public VisibilityState indicatorVisibility = VisibilityState.OnWhenPointerActive; [HideInInspector] public bool isActive = true; /// /// Emitted when the object tooltip is reset. /// public event PointerDirectionIndicatorEventHandler PointerDirectionIndicatorPositionSet; protected VRTK_ControllerEvents controllerEvents; protected Transform playArea; protected Transform headset; protected GameObject validLocation; protected GameObject invalidLocation; public virtual void OnPointerDirectionIndicatorPositionSet() { if (PointerDirectionIndicatorPositionSet != null) { PointerDirectionIndicatorPositionSet(this); } } /// /// The Initialize method is used to set up the direction indicator. /// /// The Controller Events script that is used to control the direction indicator's rotation. public virtual void Initialize(VRTK_ControllerEvents events) { controllerEvents = events; playArea = VRTK_DeviceFinder.PlayAreaTransform(); headset = VRTK_DeviceFinder.HeadsetTransform(); } /// /// The SetPosition method is used to set the world position of the direction indicator. /// /// Determines if the direction indicator GameObject should be active or not. /// The position to set the direction indicator to. public virtual void SetPosition(bool active, Vector3 position) { transform.position = position; gameObject.SetActive((isActive && active)); OnPointerDirectionIndicatorPositionSet(); } /// /// The GetRotation method returns the current reported rotation of the direction indicator. /// /// The reported rotation of the direction indicator. public virtual Quaternion GetRotation() { float offset = (includeHeadsetOffset ? playArea.eulerAngles.y - headset.eulerAngles.y : 0f); return Quaternion.Euler(0f, transform.localEulerAngles.y + offset, 0f); } /// /// The SetMaterialColor method sets the current material colour on the direction indicator. /// /// The colour to update the direction indicatormaterial to. /// Determines if the colour being set is based from a valid location or invalid location. public virtual void SetMaterialColor(Color color, bool validity) { if (validLocation != null) { validLocation.SetActive(validity); } if (invalidLocation != null) { invalidLocation.SetActive((displayOnInvalidLocation ? !validity : validity)); } if (usePointerColor) { Renderer[] renderers = GetComponentsInChildren(); for (int i = 0; i < renderers.Length; i++) { renderers[i].material.color = color; } } } /// /// The GetControllerEvents method returns the associated Controller Events script with the Pointer Direction Indicator script. /// /// The associated Controller Events script. public virtual VRTK_ControllerEvents GetControllerEvents() { return controllerEvents; } protected virtual void Awake() { validLocation = transform.Find("ValidLocation").gameObject; invalidLocation = transform.Find("InvalidLocation").gameObject; gameObject.SetActive(false); } protected virtual void Update() { if (controllerEvents != null && controllerEvents.GetAxisState(coordinateAxis, SDK_BaseController.ButtonPressTypes.Touch) && !InsideDeadzone(controllerEvents.GetAxis(coordinateAxis))) { float touchpadAngle = controllerEvents.GetAxisAngle(coordinateAxis); float angle = ((touchpadAngle > 180) ? touchpadAngle -= 360 : touchpadAngle) + headset.eulerAngles.y; transform.localEulerAngles = new Vector3(0f, angle, 0f); } } protected virtual bool InsideDeadzone(Vector2 currentAxis) { return (currentAxis == Vector2.zero || (Mathf.Abs(currentAxis.x) <= touchpadDeadzone.x && Mathf.Abs(currentAxis.y) <= touchpadDeadzone.y)); } } }