/************************************************************************************
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Extension of GraphicRaycaster to support ray casting with world space rays instead of just screen-space
|
|
/// pointer positions
|
|
/// </summary>
|
|
[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<Canvas>();
|
|
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<OVRCameraRig>();
|
|
canvas.worldCamera = rig.centerEyeAnchor.gameObject.GetComponent<Camera>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// For the given ray, find graphics on this canvas which it intersects and are not blocked by other
|
|
/// world objects
|
|
/// </summary>
|
|
[NonSerialized]
|
|
private List<RaycastHit> m_RaycastResults = new List<RaycastHit>();
|
|
private void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList, Ray ray, bool checkForBlocking)
|
|
{
|
|
//This function is closely based on
|
|
//void GraphicRaycaster.Raycast(PointerEventData eventData, List<RaycastResult> 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Performs a raycast using eventData.worldSpaceRay
|
|
/// </summary>
|
|
/// <param name="eventData"></param>
|
|
/// <param name="resultAppendList"></param>
|
|
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
|
|
{
|
|
if (eventData.IsVRPointer())
|
|
{
|
|
Raycast(eventData, resultAppendList, eventData.GetRay(), true);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Performs a raycast using the pointer object attached to this OVRRaycaster
|
|
/// </summary>
|
|
/// <param name="eventData"></param>
|
|
/// <param name="resultAppendList"></param>
|
|
public void RaycastPointer(PointerEventData eventData, List<RaycastResult> resultAppendList)
|
|
{
|
|
if (pointer != null && pointer.activeInHierarchy)
|
|
{
|
|
Raycast(eventData, resultAppendList, new Ray(eventCamera.transform.position, (pointer.transform.position - eventCamera.transform.position).normalized), false);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Perform a raycast into the screen and collect all graphics underneath it.
|
|
/// </summary>
|
|
[NonSerialized]
|
|
static readonly List<RaycastHit> s_SortedGraphics = new List<RaycastHit>();
|
|
private void GraphicRaycast(Canvas canvas, Ray ray, List<RaycastHit> results)
|
|
{
|
|
//This function is based closely on :
|
|
// void GraphicRaycaster.Raycast(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, List<Graphic> 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]);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Get screen position of worldPosition contained in this RaycastResult
|
|
/// </summary>
|
|
/// <param name="worldPosition"></param>
|
|
/// <returns></returns>
|
|
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);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Detects whether a ray intersects a RectTransform and if it does also
|
|
/// returns the world position of the intersection.
|
|
/// </summary>
|
|
/// <param name="rectTransform"></param>
|
|
/// <param name="ray"></param>
|
|
/// <param name="worldPos"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
};
|
|
|
|
|
|
/// <summary>
|
|
/// Is this the currently focussed Raycaster according to the InputModule
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|