/************************************************************************************ Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved. Licensed under the Oculus Utilities SDK License Version 1.31 (the "License"); you may not use the Utilities SDK except in compliance with the License, which is provided at the time of installation or download, or which otherwise accompanies this software in either electronic or hard copy form. You may obtain a copy of the License at https://developer.oculus.com/licenses/utilities-1.31 Unless required by applicable law or agreed to in writing, the Utilities SDK distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ************************************************************************************/ using System; using System.Collections; using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using UnityEngine.Serialization; /// /// Extension of GraphicRaycaster to support ray casting with world space rays instead of just screen-space /// pointer positions /// [RequireComponent(typeof(Canvas))] public class OVRRaycaster : GraphicRaycaster, IPointerEnterHandler { [Tooltip("A world space pointer for this canvas")] public GameObject pointer; public int sortOrder = 0; protected OVRRaycaster() { } [NonSerialized] private Canvas m_Canvas; private Canvas canvas { get { if (m_Canvas != null) return m_Canvas; m_Canvas = GetComponent(); return m_Canvas; } } public override Camera eventCamera { get { return canvas.worldCamera; } } public override int sortOrderPriority { get { return sortOrder; } } protected override void Start() { if(!canvas.worldCamera) { Debug.Log("Canvas does not have an event camera attached. Attaching OVRCameraRig.centerEyeAnchor as default."); OVRCameraRig rig = FindObjectOfType(); canvas.worldCamera = rig.centerEyeAnchor.gameObject.GetComponent(); } } /// /// For the given ray, find graphics on this canvas which it intersects and are not blocked by other /// world objects /// [NonSerialized] private List m_RaycastResults = new List(); private void Raycast(PointerEventData eventData, List resultAppendList, Ray ray, bool checkForBlocking) { //This function is closely based on //void GraphicRaycaster.Raycast(PointerEventData eventData, List resultAppendList) if (canvas == null) return; float hitDistance = float.MaxValue; if (checkForBlocking && blockingObjects != BlockingObjects.None) { float dist = eventCamera.farClipPlane; if (blockingObjects == BlockingObjects.ThreeD || blockingObjects == BlockingObjects.All) { var hits = Physics.RaycastAll(ray, dist, m_BlockingMask); if (hits.Length > 0 && hits[0].distance < hitDistance) { hitDistance = hits[0].distance; } } if (blockingObjects == BlockingObjects.TwoD || blockingObjects == BlockingObjects.All) { var hits = Physics2D.GetRayIntersectionAll(ray, dist, m_BlockingMask); if (hits.Length > 0 && hits[0].fraction * dist < hitDistance) { hitDistance = hits[0].fraction * dist; } } } m_RaycastResults.Clear(); GraphicRaycast(canvas, ray, m_RaycastResults); for (var index = 0; index < m_RaycastResults.Count; index++) { var go = m_RaycastResults[index].graphic.gameObject; bool appendGraphic = true; if (ignoreReversedGraphics) { // If we have a camera compare the direction against the cameras forward. var cameraFoward = ray.direction; var dir = go.transform.rotation * Vector3.forward; appendGraphic = Vector3.Dot(cameraFoward, dir) > 0; } // Ignore points behind us (can happen with a canvas pointer) if (eventCamera.transform.InverseTransformPoint(m_RaycastResults[index].worldPos).z <= 0) { appendGraphic = false; } if (appendGraphic) { float distance = Vector3.Distance(ray.origin, m_RaycastResults[index].worldPos); if (distance >= hitDistance) { continue; } var castResult = new RaycastResult { gameObject = go, module = this, distance = distance, index = resultAppendList.Count, depth = m_RaycastResults[index].graphic.depth, worldPosition = m_RaycastResults[index].worldPos }; resultAppendList.Add(castResult); } } } /// /// Performs a raycast using eventData.worldSpaceRay /// /// /// public override void Raycast(PointerEventData eventData, List resultAppendList) { if (eventData.IsVRPointer()) { Raycast(eventData, resultAppendList, eventData.GetRay(), true); } } /// /// Performs a raycast using the pointer object attached to this OVRRaycaster /// /// /// public void RaycastPointer(PointerEventData eventData, List resultAppendList) { if (pointer != null && pointer.activeInHierarchy) { Raycast(eventData, resultAppendList, new Ray(eventCamera.transform.position, (pointer.transform.position - eventCamera.transform.position).normalized), false); } } /// /// Perform a raycast into the screen and collect all graphics underneath it. /// [NonSerialized] static readonly List s_SortedGraphics = new List(); private void GraphicRaycast(Canvas canvas, Ray ray, List results) { //This function is based closely on : // void GraphicRaycaster.Raycast(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, List results) // But modified to take a Ray instead of a canvas pointer, and also to explicitly ignore // the graphic associated with the pointer // Necessary for the event system var foundGraphics = GraphicRegistry.GetGraphicsForCanvas(canvas); s_SortedGraphics.Clear(); for (int i = 0; i < foundGraphics.Count; ++i) { Graphic graphic = foundGraphics[i]; // -1 means it hasn't been processed by the canvas, which means it isn't actually drawn if (graphic.depth == -1 || (pointer == graphic.gameObject)) continue; Vector3 worldPos; if (RayIntersectsRectTransform(graphic.rectTransform, ray, out worldPos)) { //Work out where this is on the screen for compatibility with existing Unity UI code Vector2 screenPos = eventCamera.WorldToScreenPoint(worldPos); // mask/image intersection - See Unity docs on eventAlphaThreshold for when this does anything if (graphic.Raycast(screenPos, eventCamera)) { RaycastHit hit; hit.graphic = graphic; hit.worldPos = worldPos; hit.fromMouse = false; s_SortedGraphics.Add(hit); } } } s_SortedGraphics.Sort((g1, g2) => g2.graphic.depth.CompareTo(g1.graphic.depth)); for (int i = 0; i < s_SortedGraphics.Count; ++i) { results.Add(s_SortedGraphics[i]); } } /// /// Get screen position of worldPosition contained in this RaycastResult /// /// /// public Vector2 GetScreenPosition(RaycastResult raycastResult) { // In future versions of Uinty RaycastResult will contain screenPosition so this will not be necessary return eventCamera.WorldToScreenPoint(raycastResult.worldPosition); } /// /// Detects whether a ray intersects a RectTransform and if it does also /// returns the world position of the intersection. /// /// /// /// /// static bool RayIntersectsRectTransform(RectTransform rectTransform, Ray ray, out Vector3 worldPos) { Vector3[] corners = new Vector3[4]; rectTransform.GetWorldCorners(corners); Plane plane = new Plane(corners[0], corners[1], corners[2]); float enter; if (!plane.Raycast(ray, out enter)) { worldPos = Vector3.zero; return false; } Vector3 intersection = ray.GetPoint(enter); Vector3 BottomEdge = corners[3] - corners[0]; Vector3 LeftEdge = corners[1] - corners[0]; float BottomDot = Vector3.Dot(intersection - corners[0], BottomEdge); float LeftDot = Vector3.Dot(intersection - corners[0], LeftEdge); if (BottomDot < BottomEdge.sqrMagnitude && // Can use sqrMag because BottomEdge is not normalized LeftDot < LeftEdge.sqrMagnitude && BottomDot >= 0 && LeftDot >= 0) { worldPos = corners[0] + LeftDot * LeftEdge / LeftEdge.sqrMagnitude + BottomDot * BottomEdge / BottomEdge.sqrMagnitude; return true; } else { worldPos = Vector3.zero; return false; } } struct RaycastHit { public Graphic graphic; public Vector3 worldPos; public bool fromMouse; }; /// /// Is this the currently focussed Raycaster according to the InputModule /// /// public bool IsFocussed() { OVRInputModule inputModule = EventSystem.current.currentInputModule as OVRInputModule; return inputModule && inputModule.activeGraphicRaycaster == this; } public void OnPointerEnter(PointerEventData e) { if (e.IsVRPointer()) { // Gaze has entered this canvas. We'll make it the active one so that canvas-mouse pointer can be used. OVRInputModule inputModule = EventSystem.current.currentInputModule as OVRInputModule; if(inputModule != null) { inputModule.activeGraphicRaycaster = this; } } } }