/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * Licensed under the Oculus SDK License Agreement (the "License"); * you may not use the Oculus 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/oculussdk/ * * Unless required by applicable law or agreed to in writing, the Oculus 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; } } } }