// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; namespace UltimateXR.UI.UnityInputModule { /// /// Raycaster compatible with Unity UI to use components on canvases, enabling touch /// interaction. /// [RequireComponent(typeof(Canvas))] public class UxrFingerTipRaycaster : UxrGraphicRaycaster { #region Inspector Properties/Serialized Fields [SerializeField] private float _fingerTipMinHoverDistance = FingerTipMinHoverDistanceDefault; [SerializeField] private float _fingerTipMaxAllowedAngle = FingerTipMaxAllowedTipAngle; #endregion #region Public Types & Data /// /// Default maximum distance a can have to a canvas in order to start generating hovering /// events. /// public const float FingerTipMinHoverDistanceDefault = 0.05f; /// /// Default maximum angle between the finger and the canvas for a component to generate /// input events. /// public const float FingerTipMaxAllowedTipAngle = 65.0f; /// /// Gets or sets the maximum distance a can have to a canvas in order to generate hovering /// events. /// public float FingerTipMinHoverDistance { get => _fingerTipMinHoverDistance; set => _fingerTipMinHoverDistance = value; } #endregion #region Public Overrides GraphicRaycaster /// public override void Raycast(PointerEventData eventData, List resultAppendList) { // Check if it should be ray-casted UxrPointerEventData pointerEventData = eventData as UxrPointerEventData; if (pointerEventData == null || pointerEventData.FingerTip == null) { return; } // Initialize if necessary if (_canvas == null) { _canvas = gameObject.GetComponent(); _canvasGroup = gameObject.GetComponent(); if (_canvas == null) { return; } } if (_canvasGroup != null && _canvasGroup.interactable == false) { return; } // Raycast against the canvas, gather all results and append to the list _raycastResults.Clear(); // First check finger angle. This helps avoiding unwanted clicks. if (Vector3.Angle(pointerEventData.FingerTip.WorldDir, _canvas.transform.forward) > _fingerTipMaxAllowedAngle) { return; } // Raycast var ray = new Ray(pointerEventData.FingerTip.WorldPos, pointerEventData.FingerTip.WorldDir); Raycast(_canvas, eventCamera, ray, ref _raycastResults, ref resultAppendList); // Assign correct indices and get closest raycast RaycastResult? raycastNearest = null; for (int i = 0; i < _raycastResults.Count; ++i) { RaycastResult rayCastResult = _raycastResults[i]; rayCastResult.index = resultAppendList.Count + i; if (!raycastNearest.HasValue || rayCastResult.distance < raycastNearest.Value.distance) { raycastNearest = rayCastResult; } } // If a closest raycast was found, use it to compute the event delta if (raycastNearest.HasValue) { eventData.position = raycastNearest.Value.screenPosition; eventData.delta = eventData.position - _lastPosition; _lastPosition = eventData.position; eventData.pointerCurrentRaycast = raycastNearest.Value; } } #endregion #region Private Methods /// /// Performs a raycast to check which elements in a canvas were potentially interacted with. /// /// Canvas to check /// Camera from which to perform the checks /// Ray to use for intersection detection /// Returns the list of results sorted by increasing distance /// Returns a list where the results have been appended to the existing content private void Raycast(Canvas canvas, Camera cam, Ray ray, ref List results, ref List resultAppendList) { if (cam == null) { return; } // Iterate over all canvas graphics IList listGraphics = GraphicRegistry.GetGraphicsForCanvas(canvas); for (int i = 0; i < listGraphics.Count; ++i) { if (listGraphics[i].depth == -1 || !listGraphics[i].raycastTarget) { continue; } float distance = Vector3.Dot(listGraphics[i].transform.forward, listGraphics[i].transform.position - ray.origin) / Vector3.Dot(listGraphics[i].transform.forward, ray.direction); Vector3 position = ray.GetPoint(distance); Vector2 pointerPosition = cam.WorldToScreenPoint(position); if (distance > _fingerTipMinHoverDistance) { continue; } if (!RectTransformUtility.RectangleContainsScreenPoint(listGraphics[i].rectTransform, pointerPosition, cam)) { continue; } if (listGraphics[i].Raycast(pointerPosition, cam)) { var result = new RaycastResult { gameObject = listGraphics[i].gameObject, module = this, distance = distance, screenPosition = pointerPosition, worldPosition = position, depth = listGraphics[i].depth, sortingLayer = canvas.sortingLayerID, sortingOrder = canvas.sortingOrder, }; results.Add(result); } } results.Sort((g1, g2) => g2.depth.CompareTo(g1.depth)); resultAppendList.AddRange(results); } #endregion #region Private Types & Data private Canvas _canvas; private CanvasGroup _canvasGroup; private List _raycastResults = new List(); private Vector2 _lastPosition; #endregion } }