using System.Collections.Generic; using UnityEngine; namespace Flexalon { internal class FlexalonRaycaster { public Vector3 hitPosition; private int _raycastFrame = 0; private FlexalonInteractable _hitInteractable; private readonly Dictionary _handles = new Dictionary(); #if UNITY_UI private List _graphicRaycastResult = new List(); #endif #if UNITY_PHYSICS private RaycastHit[] _raycastHits = new RaycastHit[10]; #endif public void Register(FlexalonInteractable interactable) { _handles.Add(interactable.Handle, interactable); } public void Unregister(FlexalonInteractable interactable) { _handles.Remove(interactable.Handle); } public bool IsHit(Vector3 uiPointer, Ray ray, FlexalonInteractable interactable) { // Check if we've already casted this frame. if (_raycastFrame != Time.frameCount) { _hitInteractable = null; _raycastFrame = Time.frameCount; float minDistance = float.MaxValue; RaycastUI(uiPointer, ref minDistance); RaycastPhysics(ray, ref minDistance); } return _hitInteractable == interactable; } private void RaycastUI(Vector3 uiPointer, ref float minDistance) { #if UNITY_UI var eventSystem = UnityEngine.EventSystems.EventSystem.current; if (eventSystem) { eventSystem.RaycastAll(new UnityEngine.EventSystems.PointerEventData(eventSystem) { position = uiPointer }, _graphicRaycastResult); for (int i = 0; i < _graphicRaycastResult.Count; i++) { var hit = _graphicRaycastResult[i]; if (hit.distance < minDistance) { if (_handles.TryGetValue(hit.gameObject, out var hitInteractable)) { _hitInteractable = hitInteractable; minDistance = hit.distance; hitInteractable.UpdateCanvas(); if (hitInteractable.Canvas?.renderMode == UnityEngine.RenderMode.ScreenSpaceOverlay) { hitPosition = hit.screenPosition; } else { hitPosition = hit.worldPosition; } } } } } #endif } private void RaycastPhysics(Ray ray, ref float minDistance) { #if UNITY_PHYSICS int hits = Physics.RaycastNonAlloc(ray, _raycastHits, 1000); // Find the nearest hit interactable. for (int i = 0; i < hits; i++) { var hit = _raycastHits[i]; if (hit.distance < minDistance && _handles.TryGetValue(hit.collider.gameObject, out var hitInteractable)) { _hitInteractable = hitInteractable; minDistance = hit.distance; hitPosition = hit.point; } } #endif } } }