//////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2007-2020 , Inc. All Rights Reserved. // //////////////////////////////////////////////////////////////////////////////// using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; using GCSeries.Core.EventSystems; namespace GCSeries.Core.UI { [RequireComponent(typeof(Canvas))] public class ZGraphicRaycaster : GraphicRaycaster { //////////////////////////////////////////////////////////////////////// // MonoBehaviour Callbacks //////////////////////////////////////////////////////////////////////// protected override void OnEnable() { base.OnEnable(); if (!s_instances.Contains(this)) { s_instances.Add(this); } } protected override void OnDisable() { base.OnDisable(); if (s_instances.Contains(this)) { s_instances.Remove(this); } } protected override void Start() { base.Start(); if (this.Canvas.renderMode == RenderMode.WorldSpace && this.Canvas.worldCamera == null) { Debug.LogWarning( "No Event Camera found attached to associated world " + "space canvas. Please make sure to assign an appropriate " + "camera to your canvas to minimize performance impact " + "and ensure raycasts are performed correctly.", this); } } //////////////////////////////////////////////////////////////////////// // Public Properties //////////////////////////////////////////////////////////////////////// public Canvas Canvas { get { if (this._canvas == null) { this._canvas = this.GetComponent(); } return this._canvas; } } //////////////////////////////////////////////////////////////////////// // Public Methods //////////////////////////////////////////////////////////////////////// /// /// Gets a list of all enabled ZGraphicRaycaster instances in the scene. /// /// /// /// The list of all enabled ZGraphicRaycasters instances in the scene. /// public static IList GetRaycasters() { return s_instances; } /// /// Performs a raycast against all enabled ZGraphicRaycaster instances /// in the scene and reports the closest hit. /// /// /// /// The starting point and direction of the ray. /// /// /// The raycast result corresponding to the closest hit. /// /// /// The maximum distance that the hit result is allowed to be from /// the start of the ray. /// /// /// A layer mask that is used to selectively ignore graphics when /// casting the ray. /// /// /// /// True if a graphic was hit. False otherwise. /// public static bool Raycast( Ray ray, out RaycastResult result, float maxDistance, int layerMask) { s_results.Clear(); for (int i = 0; i < s_instances.Count; ++i) { s_instances[i].Raycast(ray, s_results, maxDistance, layerMask); } if (s_results.Count > 0) { result = s_results[0]; return true; } result = default(RaycastResult); return false; } /// /// Performs a raycast against all enabled ZGraphicRaycaster instances /// in the scene and reports all hits. /// /// /// /// The starting point and direction of the ray. /// /// /// The raycast results corresponding to all hits. /// /// /// The maximum distance that a hit result is allowed to be from /// the start of the ray. /// /// /// A layer mask that is used to selectively ignore graphics when /// casting the ray. /// /// /// /// True if a graphic was hit. False otherwise. /// public static void RaycastAll( Ray ray, List resultAppendList, float maxDistance, int layerMask) { for (int i = 0; i < s_instances.Count; ++i) { s_instances[i].Raycast( ray, resultAppendList, maxDistance, layerMask); } } public override void Raycast( PointerEventData eventData, List resultAppendList) { ZPointerEventData e = eventData as ZPointerEventData; if (e != null) { this.Raycast( e.Pointer.PointerRay, resultAppendList, float.PositiveInfinity, ~0); } else { base.Raycast(eventData, resultAppendList); } } //////////////////////////////////////////////////////////////////////// // Private Methods //////////////////////////////////////////////////////////////////////// private void Raycast( Ray ray, List resultAppendList, float maxDistance, int layerMask) { // Potentially reduce the maximum hit distance based on whether // any 2D or 3D blocking objects have been intersected. float distance = this.eventCamera.farClipPlane - this.eventCamera.nearClipPlane; if (this.blockingObjects == BlockingObjects.ThreeD || this.blockingObjects == BlockingObjects.All) { RaycastHit hit; if (Physics.Raycast(ray, out hit, distance, this.m_BlockingMask)) { maxDistance = Mathf.Min(hit.distance, maxDistance); } } if (this.blockingObjects == BlockingObjects.TwoD || this.blockingObjects == BlockingObjects.All) { RaycastHit2D hit = Physics2D.GetRayIntersection( ray, distance, this.m_BlockingMask); if (hit.collider != null) { maxDistance = Mathf.Min( hit.fraction * distance, maxDistance); } } // Retrieve the list of graphics associated with the canvas. IList graphics = GraphicRegistry.GetGraphicsForCanvas(this.Canvas); // Iterate through each of graphics and perform hit tests. for (int i = 0; i < graphics.Count; ++i) { Graphic graphic = graphics[i]; // Skip the graphic if it's not in the layer mask. if (((1 << graphic.gameObject.layer) & layerMask) == 0) { continue; } // Perform a raycast against the graphic. RaycastResult result; if (this.Raycast(ray, graphic, out result, maxDistance)) { resultAppendList.Add(result); } } // Sort the results by depth. resultAppendList.Sort((x, y) => y.depth.CompareTo(x.depth)); } private bool Raycast( Ray ray, Graphic graphic, out RaycastResult result, float maxDistance) { result = default(RaycastResult); // Skip graphics that aren't raycast targets. if (!graphic.raycastTarget) { return false; } // Skip graphics that aren't being drawn. if (graphic.depth == -1) { return false; } // Skip graphics that are reversed if the ignore reversed // graphics inspector field is enabled. if (this.ignoreReversedGraphics && Vector3.Dot(ray.direction, -graphic.transform.forward) > 0) { return false; } // Create a plane based on the graphic's transform. Plane plane = new Plane( -graphic.transform.forward, graphic.transform.position); // Skip graphics that failed the plane intersection test. float distance = 0.0f; if (!plane.Raycast(ray, out distance)) { return false; } // Skip graphics that are further away than the max distance. if (distance > maxDistance) { return false; } Vector3 worldPosition = ray.origin + (ray.direction * distance); Vector3 screenPosition = this.eventCamera.WorldToScreenPoint(worldPosition); // Skip graphics that have failed the bounds test. if (!RectTransformUtility.RectangleContainsScreenPoint( graphic.rectTransform, screenPosition, this.eventCamera)) { return false; } // Skip graphics that fail the raycast test. // NOTE: This is necessary to ensure that raycasts against // masked out areas of the graphic are correctly ignored. if (!graphic.Raycast(screenPosition, this.eventCamera)) { return false; } result.depth = graphic.depth; result.distance = distance; result.worldPosition = worldPosition; result.worldNormal = plane.normal; result.screenPosition = screenPosition; result.gameObject = graphic.gameObject; result.module = this; return true; } //////////////////////////////////////////////////////////////////////// // Private Members //////////////////////////////////////////////////////////////////////// private static readonly List s_instances = new List(); private static readonly List s_results = new List(); private Canvas _canvas = null; } }