Splash
Into Environment – Default Settings – (init config)
Tap – brings up Main Menu. (optional info dialogs – modal dialog – on by default)
Tools – four on top
0) default navigation – blue circle – navigation menu active
1) grabber – selects object holds and places at desired location Menus/waypoints/transforms
2) paint – current paint mode
3) operating mode
ParticleGazeCursor
using UnityEngine; using System.Collections; [RequireComponent(typeof(OVRGazePointer))] public class ParticleGazeCursor : MonoBehaviour { public float emissionScale; public float maxSpeed; [Header("Particle emission curves")] // The scale on the x axis of the curves runs from 0 to maxSpeed [Tooltip("Curve for trailing edge of pointer")] public AnimationCurve halfEmission; [Tooltip("Curve for full perimeter of pointer")] public AnimationCurve fullEmission; [Tooltip("Curve for full perimeter of pointer")] public bool particleTrail; public float particleScale = 0.68f; Vector3 lastPos; ParticleSystem psHalf; ParticleSystem psFull; MeshRenderer quadRenderer; Color particleStartColor; OVRGazePointer gazePointer; // Use this for initialization void Start() { gazePointer = GetComponent<OVRGazePointer>(); foreach (Transform child in transform) { if (child.name.Equals("Half")) psHalf = child.GetComponent<ParticleSystem>(); if (child.name.Equals("Full")) psFull = child.GetComponent<ParticleSystem>(); if (child.name.Equals("Quad")) quadRenderer = child.GetComponent<MeshRenderer>(); } float scale = transform.lossyScale.x; psHalf.startSize *= scale; psHalf.startSpeed *= scale; psFull.startSize *= scale; psFull.startSpeed *= scale; particleStartColor = psFull.startColor; if (!particleTrail) { GameObject.Destroy(psHalf); GameObject.Destroy(psFull); } } // Update is called once per frame void Update() { var delta = GetComponent<OVRGazePointer>().positionDelta; if (particleTrail) { // Evaluate these curves to decide the emission rate of the two sources of particles. psHalf.emissionRate = halfEmission.Evaluate((delta.magnitude / Time.deltaTime) / maxSpeed) * emissionScale; psFull.emissionRate = fullEmission.Evaluate((delta.magnitude / Time.deltaTime) / maxSpeed) * emissionScale; // Make the particles fade out with visibitly the same way the main ring does Color color = particleStartColor; color.a = gazePointer.visibilityStrength; psHalf.startColor = color; psFull.startColor = color; // Particles also scale when the gaze pointer scales psFull.startSize = particleScale * transform.lossyScale.x; psHalf.startSize = particleScale * transform.lossyScale.x; } // Set the main pointers alpha value to the correct level to achieve the desired level of fade quadRenderer.material.SetColor("_TintColor",new Color(1, 1, 1, gazePointer.visibilityStrength)); } }
OVRGazePointer
using UnityEngine; using System.Collections; public class OVRGazePointer : MonoBehaviour { [Tooltip("Should the pointer be hidden when not over interactive objects.")] public bool hideByDefault = true; [Tooltip("Time after leaving interactive object before pointer fades.")] public float showTimeoutPeriod = 1; [Tooltip("Time after mouse pointer becoming inactive before pointer unfades.")] public float hideTimeoutPeriod = 0.1f; [Tooltip("Keep a faint version of the pointer visible while using a mouse")] public bool dimOnHideRequest = true; [Tooltip("Angular scale of pointer")] public float depthScaleMultiplier = 0.03f; [Tooltip("Used for positioning pointer in scene")] public OVRCameraRig cameraRig; /// <summary> /// Is gaze pointer current visible /// </summary> public bool hidden { get; private set; } /// <summary> /// Current scale applied to pointer /// </summary> public float currentScale { get; private set; } /// <summary> /// Current depth of pointer from camera /// </summary> private float depth; /// <summary> /// How many times position has been set this frame. Used to detect when there are no position sets in a frame. /// </summary> private int positionSetsThisFrame = 0; /// <summary> /// Position last frame. /// </summary> private Vector3 lastPosition; /// <summary> /// Last time code requested the pointer be shown. Usually when pointer passes over interactive elements. /// </summary> private float lastShowRequestTime; /// <summary> /// Last time pointer was requested to be hidden. Usually mouse pointer activity. /// </summary> private float lastHideRequestTime; // How much the gaze pointer moved in the last frame private Vector3 _positionDelta; public Vector3 positionDelta { get { return _positionDelta; } } private static OVRGazePointer _instance; public static OVRGazePointer instance { // If there's no GazePointer already in the scene, instanciate one now. get { if (_instance == null) { Debug.Log(string.Format("Instanciating GazePointer", 0)); _instance = (OVRGazePointer)GameObject.Instantiate((OVRGazePointer)Resources.Load("Prefabs/GazePointerRing", typeof(OVRGazePointer))); } return _instance; } } public float visibilityStrength { get { float strengthFromShowRequest; if (hideByDefault) { strengthFromShowRequest = Mathf.Clamp01(1 - (Time.time - lastShowRequestTime) / showTimeoutPeriod); } else { strengthFromShowRequest = 1; } // Now consider factors requesting pointer to be hidden float strengthFromHideRequest; if (dimOnHideRequest) { strengthFromHideRequest = (lastHideRequestTime + hideTimeoutPeriod > Time.time) ? 0.1f : 1; } else { strengthFromHideRequest = (lastHideRequestTime + hideTimeoutPeriod > Time.time) ? 0 : 1; } // Hide requests take priority return Mathf.Min(strengthFromShowRequest, strengthFromHideRequest); } } private void Awake() { currentScale = 1; // Only allow one instance at runtime. if (_instance != null && _instance != this) { enabled = false; DestroyImmediate(this); return; } _instance = this; } // Update is called once per frame void Update () { // Even if this runs after SetPosition, it will work out to be the same position // Keep pointer at same distance from camera rig transform.position = cameraRig.centerEyeAnchor.transform.position + cameraRig.centerEyeAnchor.transform.forward * depth; if (visibilityStrength == 0 && !hidden) { Hide(); } else if (visibilityStrength > 0 && hidden) { Show(); } } /// <summary> /// Set position and orientation of pointer /// </summary> /// <param name="pos"></param> /// <param name="normal"></param> public void SetPosition(Vector3 pos, Vector3 normal) { transform.position = pos; // Set the rotation to match the normal of the surface it's on. For the other degree of freedom use // the direction of movement so that trail effects etc are easier Quaternion newRot = transform.rotation; newRot.SetLookRotation(normal, Vector3.up);//(lastPosition - transform.position).normalized transform.rotation = newRot; // record depth so that distance doesn't pop when pointer leaves an object depth = (cameraRig.centerEyeAnchor.transform.position - pos).magnitude; //set scale based on depth currentScale = depth * depthScaleMultiplier; transform.localScale = new Vector3(currentScale, currentScale, currentScale); positionSetsThisFrame++; } /// <summary> /// SetPosition overload without normal. Just makes cursor face user /// </summary> /// <param name="pos"></param> public void SetPosition(Vector3 pos) { SetPosition(pos, cameraRig.centerEyeAnchor.transform.forward); } void LateUpdate() { // This happens after all Updates so we know nothing set the position this frame if (positionSetsThisFrame == 0) { // No geometry intersections, so gazing into space. Make the cursor face directly at the camera Quaternion newRot = transform.rotation; newRot.SetLookRotation(cameraRig.centerEyeAnchor.transform.forward, Vector3.up);//(lastPosition - transform.position).normalized transform.rotation = newRot; } // Keep track of cursor movement direction _positionDelta = transform.position - lastPosition; lastPosition = transform.position; positionSetsThisFrame = 0; } /// <summary> /// Request the pointer be hidden /// </summary> public void RequestHide() { if (!dimOnHideRequest) { Hide(); } lastHideRequestTime = Time.time; } /// <summary> /// Request the pointer be shown. Hide requests take priority /// </summary> public void RequestShow() { Show(); lastShowRequestTime = Time.time; } private void Hide() { foreach (Transform child in transform) { child.gameObject.SetActive(false); } if (GetComponent<Renderer>()) GetComponent<Renderer>().enabled = false; hidden = true; } private void Show() { foreach (Transform child in transform) { child.gameObject.SetActive(true); } if (GetComponent<Renderer>()) GetComponent<Renderer>().enabled = true; hidden = false; } }
OVRRaycaster
using System; using System.Collections; using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using UnityEngine.Serialization; [RequireComponent(typeof(Canvas))] public class OVRRaycaster : GraphicRaycaster, IPointerEnterHandler { [Tooltip("A world space pointer for this canvas")] public GameObject pointer; 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; } } /// <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) { OVRRayPointerEventData rayPointerEventData = eventData as OVRRayPointerEventData; if (rayPointerEventData != null) { Raycast(eventData, resultAppendList, rayPointerEventData.worldSpaceRay, 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) { 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) { PointerEventData ped = e as OVRRayPointerEventData; if (ped != null) { // 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; inputModule.activeGraphicRaycaster = this; } } }
OVRPhysicsRaycaster
using System.Collections.Generic; namespace UnityEngine.EventSystems { /// <summary> /// Simple event system using physics raycasts. /// </summary> [RequireComponent(typeof(OVRCameraRig))] public class OVRPhysicsRaycaster : BaseRaycaster { /// <summary> /// Const to use for clarity when no event mask is set /// </summary> protected const int kNoEventMaskSet = -1; /// <summary> /// Layer mask used to filter events. Always combined with the camera's culling mask if a camera is used. /// </summary> [SerializeField] protected LayerMask m_EventMask = kNoEventMaskSet; protected OVRPhysicsRaycaster() { } public override Camera eventCamera { get { return GetComponent<OVRCameraRig>().leftEyeCamera; } } /// <summary> /// Depth used to determine the order of event processing. /// </summary> public virtual int depth { get { return (eventCamera != null) ? (int)eventCamera.depth : 0xFFFFFF; } } /// <summary> /// Event mask used to determine which objects will receive events. /// </summary> public int finalEventMask { get { return (eventCamera != null) ? eventCamera.cullingMask & m_EventMask : kNoEventMaskSet; } } /// <summary> /// Layer mask used to filter events. Always combined with the camera's culling mask if a camera is used. /// </summary> public LayerMask eventMask { get { return m_EventMask; } set { m_EventMask = value; } } /// <summary> /// Perform a raycast using the worldSpaceRay in eventData. /// </summary> /// <param name="eventData"></param> /// <param name="resultAppendList"></param> public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList) { // This function is closely based on PhysicsRaycaster.Raycast if (eventCamera == null) return; OVRRayPointerEventData rayPointerEventData = eventData as OVRRayPointerEventData; if (rayPointerEventData == null) return; var ray = rayPointerEventData.worldSpaceRay; float dist = eventCamera.farClipPlane - eventCamera.nearClipPlane; var hits = Physics.RaycastAll(ray, dist, finalEventMask); if (hits.Length > 1) System.Array.Sort(hits, (r1, r2) => r1.distance.CompareTo(r2.distance)); if (hits.Length != 0) { for (int b = 0, bmax = hits.Length; b < bmax; ++b) { var result = new RaycastResult { gameObject = hits[b].collider.gameObject, module = this, distance = hits[b].distance, index = resultAppendList.Count, worldPosition = hits[0].point, worldNormal = hits[0].normal, }; resultAppendList.Add(result); } } } /// <summary> /// Perform a Spherecast using the worldSpaceRay in eventData. /// </summary> /// <param name="eventData"></param> /// <param name="resultAppendList"></param> /// <param name="radius">Radius of the sphere</param> public void Spherecast(PointerEventData eventData, List<RaycastResult> resultAppendList, float radius) { if (eventCamera == null) return; OVRRayPointerEventData rayPointerEventData = eventData as OVRRayPointerEventData; if (rayPointerEventData == null) return; var ray = rayPointerEventData.worldSpaceRay; float dist = eventCamera.farClipPlane - eventCamera.nearClipPlane; var hits = Physics.SphereCastAll(ray, radius, dist, finalEventMask); if (hits.Length > 1) System.Array.Sort(hits, (r1, r2) => r1.distance.CompareTo(r2.distance)); if (hits.Length != 0) { for (int b = 0, bmax = hits.Length; b < bmax; ++b) { var result = new RaycastResult { gameObject = hits[b].collider.gameObject, module = this, distance = hits[b].distance, index = resultAppendList.Count, worldPosition = hits[0].point, worldNormal = hits[0].normal, }; resultAppendList.Add(result); } } } /// <summary> /// Get screen position of this world position as seen by the event camera of this OVRPhysicsRaycaster /// </summary> /// <param name="worldPosition"></param> /// <returns></returns> public Vector2 GetScreenPos(Vector3 worldPosition) { // In future versions of Uinty RaycastResult will contain screenPosition so this will not be necessary return eventCamera.WorldToScreenPoint(worldPosition); } } }
OVRInputModule
using System; using System.Collections.Generic; namespace UnityEngine.EventSystems { public class OVRInputModule : PointerInputModule { [Tooltip("Object which points with Z axis. E.g. CentreEyeAnchor from OVRCameraRig")] public Transform rayTransform; [Tooltip("Gamepad button to act as gaze click")] public OVRInput.Button joyPadClickButton = OVRInput.Button.One; [Tooltip("Keyboard button to act as gaze click")] public KeyCode gazeClickKey = KeyCode.Space; [Header("Physics")] [Tooltip("Perform an sphere cast to determine correct depth for gaze pointer")] public bool performSphereCastForGazepointer; [Tooltip("Match the gaze pointer normal to geometry normal for physics colliders")] public bool matchNormalOnPhysicsColliders; [Header("Gamepad Stick Scroll")] [Tooltip("Enable scrolling with the left stick on a gamepad")] public bool useLeftStickScroll = true; [Tooltip("Deadzone for left stick to prevent accidental scrolling")] public float leftStickDeadZone = 0.15f; [Header("Touchpad Swipe Scroll")] [Tooltip("Enable scrolling by swiping the GearVR touchpad")] public bool useSwipeScroll = true; [Tooltip("Minimum swipe amount to trigger scrolling")] public float minSwipeMovement = 0; [Tooltip("Distance scrolled when swipe scroll occurs")] public float swipeScrollScale = 4f; #region GearVR swipe scroll private Vector2 swipeStartPos; private Vector2 unusedSwipe; #endregion // The raycaster that gets to do pointer interaction (e.g. with a mouse), gaze interaction always works // private OVRRaycaster _activeGraphicRaycaster; [NonSerialized] public OVRRaycaster activeGraphicRaycaster; [Header("Dragging")] [Tooltip("Minimum pointer movement in degrees to start dragging")] public float angleDragThreshold = 1; // The following region contains code exactly the same as the implementation // of StandaloneInputModule. It is copied here rather than inheriting from StandaloneInputModule // because most of StandaloneInputModule is private so it isn't possible to easily derive from. // Future changes from Unity to StandaloneInputModule will make it possible for this class to // derive from StandaloneInputModule instead of PointerInput module. // // The following functions are not present in the following region since they have modified // versions in the next region: // Process // ProcessMouseEvent // UseMouse #region StandaloneInputModule code private float m_NextAction; private Vector2 m_LastMousePosition; private Vector2 m_MousePosition; protected OVRInputModule() {} void Reset() { allowActivationOnMobileDevice = true; } [Obsolete("Mode is no longer needed on input module as it handles both mouse and keyboard simultaneously.", false)] public enum InputMode { Mouse, Buttons } [Obsolete("Mode is no longer needed on input module as it handles both mouse and keyboard simultaneously.", false)] public InputMode inputMode { get { return InputMode.Mouse; } } [Header("Standalone Input Module")] [SerializeField] private string m_HorizontalAxis = "Horizontal"; /// <summary> /// Name of the vertical axis for movement (if axis events are used). /// </summary> [SerializeField] private string m_VerticalAxis = "Vertical"; /// <summary> /// Name of the submit button. /// </summary> [SerializeField] private string m_SubmitButton = "Submit"; /// <summary> /// Name of the submit button. /// </summary> [SerializeField] private string m_CancelButton = "Cancel"; [SerializeField] private float m_InputActionsPerSecond = 10; [SerializeField] private bool m_AllowActivationOnMobileDevice; public bool allowActivationOnMobileDevice { get { return m_AllowActivationOnMobileDevice; } set { m_AllowActivationOnMobileDevice = value; } } public float inputActionsPerSecond { get { return m_InputActionsPerSecond; } set { m_InputActionsPerSecond = value; } } /// <summary> /// Name of the horizontal axis for movement (if axis events are used). /// </summary> public string horizontalAxis { get { return m_HorizontalAxis; } set { m_HorizontalAxis = value; } } /// <summary> /// Name of the vertical axis for movement (if axis events are used). /// </summary> public string verticalAxis { get { return m_VerticalAxis; } set { m_VerticalAxis = value; } } public string submitButton { get { return m_SubmitButton; } set { m_SubmitButton = value; } } public string cancelButton { get { return m_CancelButton; } set { m_CancelButton = value; } } public override void UpdateModule() { m_LastMousePosition = m_MousePosition; m_MousePosition = Input.mousePosition; } public override bool IsModuleSupported() { // Check for mouse presence instead of whether touch is supported, // as you can connect mouse to a tablet and in that case we'd want // to use StandaloneInputModule for non-touch input events. return m_AllowActivationOnMobileDevice || Input.mousePresent; } public override bool ShouldActivateModule() { if (!base.ShouldActivateModule()) return false; var shouldActivate = Input.GetButtonDown(m_SubmitButton); shouldActivate |= Input.GetButtonDown(m_CancelButton); shouldActivate |= !Mathf.Approximately(Input.GetAxisRaw(m_HorizontalAxis), 0.0f); shouldActivate |= !Mathf.Approximately(Input.GetAxisRaw(m_VerticalAxis), 0.0f); shouldActivate |= (m_MousePosition - m_LastMousePosition).sqrMagnitude > 0.0f; shouldActivate |= Input.GetMouseButtonDown(0); return shouldActivate; } public override void ActivateModule() { base.ActivateModule(); m_MousePosition = Input.mousePosition; m_LastMousePosition = Input.mousePosition; var toSelect = eventSystem.currentSelectedGameObject; if (toSelect == null) toSelect = eventSystem.firstSelectedGameObject; eventSystem.SetSelectedGameObject(toSelect, GetBaseEventData()); } public override void DeactivateModule() { base.DeactivateModule(); ClearSelection(); } /// <summary> /// Process submit keys. /// </summary> private bool SendSubmitEventToSelectedObject() { if (eventSystem.currentSelectedGameObject == null) return false; var data = GetBaseEventData(); if (Input.GetButtonDown(m_SubmitButton)) ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.submitHandler); if (Input.GetButtonDown(m_CancelButton)) ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.cancelHandler); return data.used; } private bool AllowMoveEventProcessing(float time) { bool allow = Input.GetButtonDown(m_HorizontalAxis); allow |= Input.GetButtonDown(m_VerticalAxis); allow |= (time > m_NextAction); return allow; } private Vector2 GetRawMoveVector() { Vector2 move = Vector2.zero; move.x = Input.GetAxisRaw(m_HorizontalAxis); move.y = Input.GetAxisRaw(m_VerticalAxis); if (Input.GetButtonDown(m_HorizontalAxis)) { if (move.x < 0) move.x = -1f; if (move.x > 0) move.x = 1f; } if (Input.GetButtonDown(m_VerticalAxis)) { if (move.y < 0) move.y = -1f; if (move.y > 0) move.y = 1f; } return move; } /// <summary> /// Process keyboard events. /// </summary> private bool SendMoveEventToSelectedObject() { float time = Time.unscaledTime; if (!AllowMoveEventProcessing(time)) return false; Vector2 movement = GetRawMoveVector(); // Debug.Log(m_ProcessingEvent.rawType + " axis:" + m_AllowAxisEvents + " value:" + "(" + x + "," + y + ")"); var axisEventData = GetAxisEventData(movement.x, movement.y, 0.6f); if (!Mathf.Approximately(axisEventData.moveVector.x, 0f) || !Mathf.Approximately(axisEventData.moveVector.y, 0f)) { ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, axisEventData, ExecuteEvents.moveHandler); } m_NextAction = time + 1f / m_InputActionsPerSecond; return axisEventData.used; } private bool SendUpdateEventToSelectedObject() { if (eventSystem.currentSelectedGameObject == null) return false; var data = GetBaseEventData(); ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.updateSelectedHandler); return data.used; } /// <summary> /// Process the current mouse press. /// </summary> private void ProcessMousePress(MouseButtonEventData data) { var pointerEvent = data.buttonData; var currentOverGo = pointerEvent.pointerCurrentRaycast.gameObject; // PointerDown notification if (data.PressedThisFrame()) { pointerEvent.eligibleForClick = true; pointerEvent.delta = Vector2.zero; pointerEvent.dragging = false; pointerEvent.useDragThreshold = true; pointerEvent.pressPosition = pointerEvent.position; pointerEvent.pointerPressRaycast = pointerEvent.pointerCurrentRaycast; DeselectIfSelectionChanged(currentOverGo, pointerEvent); // search for the control that will receive the press // if we can't find a press handler set the press // handler to be what would receive a click. var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.pointerDownHandler); // didnt find a press handler... search for a click handler if (newPressed == null) newPressed = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo); // Debug.Log("Pressed: " + newPressed); float time = Time.unscaledTime; if (newPressed == pointerEvent.lastPress) { var diffTime = time - pointerEvent.clickTime; if (diffTime < 0.3f) ++pointerEvent.clickCount; else pointerEvent.clickCount = 1; pointerEvent.clickTime = time; } else { pointerEvent.clickCount = 1; } pointerEvent.pointerPress = newPressed; pointerEvent.rawPointerPress = currentOverGo; pointerEvent.clickTime = time; // Save the drag handler as well pointerEvent.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(currentOverGo); if (pointerEvent.pointerDrag != null) ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.initializePotentialDrag); } // PointerUp notification if (data.ReleasedThisFrame()) { // Debug.Log("Executing pressup on: " + pointer.pointerPress); ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler); // Debug.Log("KeyCode: " + pointer.eventData.keyCode); // see if we mouse up on the same element that we clicked on... var pointerUpHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo); // PointerClick and Drop events if (pointerEvent.pointerPress == pointerUpHandler && pointerEvent.eligibleForClick) { ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerClickHandler); } else if (pointerEvent.pointerDrag != null) { ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.dropHandler); } pointerEvent.eligibleForClick = false; pointerEvent.pointerPress = null; pointerEvent.rawPointerPress = null; if (pointerEvent.pointerDrag != null && pointerEvent.dragging) ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.endDragHandler); pointerEvent.dragging = false; pointerEvent.pointerDrag = null; // redo pointer enter / exit to refresh state // so that if we moused over somethign that ignored it before // due to having pressed on something else // it now gets it. if (currentOverGo != pointerEvent.pointerEnter) { HandlePointerExitAndEnter(pointerEvent, null); HandlePointerExitAndEnter(pointerEvent, currentOverGo); } } } #endregion #region Modified StandaloneInputModule methods /// <summary> /// Process all mouse events. This is the same as the StandaloneInputModule version except that /// it takes MouseState as a parameter, allowing it to be used for both Gaze and Mouse /// pointerss. /// </summary> private void ProcessMouseEvent(MouseState mouseData) { var pressed = mouseData.AnyPressesThisFrame(); var released = mouseData.AnyReleasesThisFrame(); var leftButtonData = mouseData.GetButtonState(PointerEventData.InputButton.Left).eventData; if (!UseMouse(pressed, released, leftButtonData.buttonData)) return; // Process the first mouse button fully ProcessMousePress(leftButtonData); ProcessMove(leftButtonData.buttonData); ProcessDrag(leftButtonData.buttonData); // Now process right / middle clicks ProcessMousePress(mouseData.GetButtonState(PointerEventData.InputButton.Right).eventData); ProcessDrag(mouseData.GetButtonState(PointerEventData.InputButton.Right).eventData.buttonData); ProcessMousePress(mouseData.GetButtonState(PointerEventData.InputButton.Middle).eventData); ProcessDrag(mouseData.GetButtonState(PointerEventData.InputButton.Middle).eventData.buttonData); if (!Mathf.Approximately(leftButtonData.buttonData.scrollDelta.sqrMagnitude, 0.0f)) { var scrollHandler = ExecuteEvents.GetEventHandler<IScrollHandler>(leftButtonData.buttonData.pointerCurrentRaycast.gameObject); ExecuteEvents.ExecuteHierarchy(scrollHandler, leftButtonData.buttonData, ExecuteEvents.scrollHandler); } } /// <summary> /// Process this InputModule. Same as the StandaloneInputModule version, except that it calls /// ProcessMouseEvent twice, once for gaze pointers, and once for mouse pointers. /// </summary> public override void Process() { bool usedEvent = SendUpdateEventToSelectedObject(); if (eventSystem.sendNavigationEvents) { if (!usedEvent) usedEvent |= SendMoveEventToSelectedObject(); if (!usedEvent) SendSubmitEventToSelectedObject(); } ProcessMouseEvent(GetGazePointerData()); #if !UNITY_ANDROID ProcessMouseEvent(GetCanvasPointerData()); #endif } /// <summary> /// Decide if mouse events need to be processed this frame. Same as StandloneInputModule except /// that the IsPointerMoving method from this class is used, instead of the method on PointerEventData /// </summary> private static bool UseMouse(bool pressed, bool released, PointerEventData pointerData) { if (pressed || released || IsPointerMoving(pointerData) || pointerData.IsScrolling()) return true; return false; } #endregion /// <summary> /// Convenience function for cloning PointerEventData /// </summary> /// <param name="from">Copy this value</param> /// <param name="to">to this object</param> protected void CopyFromTo(OVRRayPointerEventData @from, OVRRayPointerEventData @to) { @to.position = @from.position; @to.delta = @from.delta; @to.scrollDelta = @from.scrollDelta; @to.pointerCurrentRaycast = @from.pointerCurrentRaycast; @to.pointerEnter = @from.pointerEnter; @to.worldSpaceRay = @from.worldSpaceRay; } /// <summary> /// Convenience function for cloning PointerEventData /// </summary> /// <param name="from">Copy this value</param> /// <param name="to">to this object</param> protected void CopyFromTo(PointerEventData @from, PointerEventData @to) { @to.position = @from.position; @to.delta = @from.delta; @to.scrollDelta = @from.scrollDelta; @to.pointerCurrentRaycast = @from.pointerCurrentRaycast; @to.pointerEnter = @from.pointerEnter; } // In the following region we extend the PointerEventData system implemented in PointerInputModule // We define an additional dictionary for ray(e.g. gaze) based pointers. Mouse pointers still use the dictionary // in PointerInputModule #region PointerEventData pool // Pool for OVRRayPointerEventData for ray based pointers protected Dictionary<int, OVRRayPointerEventData> m_VRRayPointerData = new Dictionary<int, OVRRayPointerEventData>(); protected bool GetPointerData(int id, out OVRRayPointerEventData data, bool create) { if (!m_VRRayPointerData.TryGetValue(id, out data) && create) { data = new OVRRayPointerEventData(eventSystem) { pointerId = id, }; m_VRRayPointerData.Add(id, data); return true; } return false; } /// <summary> /// Clear pointer state for both types of pointer /// </summary> protected new void ClearSelection() { var baseEventData = GetBaseEventData(); foreach (var pointer in m_PointerData.Values) { // clear all selection HandlePointerExitAndEnter(pointer, null); } foreach (var pointer in m_VRRayPointerData.Values) { // clear all selection HandlePointerExitAndEnter(pointer, null); } m_PointerData.Clear(); eventSystem.SetSelectedGameObject(null, baseEventData); } #endregion /// <summary> /// For RectTransform, calculate it's normal in world space /// </summary> static Vector3 GetRectTransformNormal(RectTransform rectTransform) { Vector3[] corners = new Vector3[4]; rectTransform.GetWorldCorners(corners); Vector3 BottomEdge = corners[3] - corners[0]; Vector3 LeftEdge = corners[1] - corners[0]; rectTransform.GetWorldCorners(corners); return Vector3.Cross(LeftEdge, BottomEdge).normalized; } private readonly MouseState m_MouseState = new MouseState(); // Overridden so that we can process the two types of pointer separately // The following 2 functions are equivalent to PointerInputModule.GetMousePointerEventData but are customized to // get data for ray pointers and canvas mouse pointers. /// <summary> /// State for a pointer controlled by a world space ray. E.g. gaze pointer /// </summary> /// <returns></returns> protected MouseState GetGazePointerData() { // Get the OVRRayPointerEventData reference OVRRayPointerEventData leftData; GetPointerData(kMouseLeftId, out leftData, true ); leftData.Reset(); //Now set the world space ray. This ray is what the user uses to point at UI elements leftData.worldSpaceRay = new Ray(rayTransform.position, rayTransform.forward); leftData.scrollDelta = GetExtraScrollDelta(); //Populate some default values leftData.button = PointerEventData.InputButton.Left; leftData.useDragThreshold = true; // Perform raycast to find intersections with world eventSystem.RaycastAll(leftData, m_RaycastResultCache); var raycast = FindFirstRaycast(m_RaycastResultCache); leftData.pointerCurrentRaycast = raycast; m_RaycastResultCache.Clear(); OVRRaycaster ovrRaycaster = raycast.module as OVRRaycaster; // We're only interested in intersections from OVRRaycasters if (ovrRaycaster) { // The Unity UI system expects event data to have a screen position // so even though this raycast came from a world space ray we must get a screen // space position for the camera attached to this raycaster for compatability leftData.position = ovrRaycaster.GetScreenPosition(raycast); // Find the world position and normal the Graphic the ray intersected RectTransform graphicRect = raycast.gameObject.GetComponent<RectTransform>(); if (graphicRect != null) { // Set are gaze indicator with this world position and normal Vector3 worldPos = raycast.worldPosition; Vector3 normal = GetRectTransformNormal(graphicRect); OVRGazePointer.instance.SetPosition(worldPos, normal); // Make sure it's being shown OVRGazePointer.instance.RequestShow(); } } OVRPhysicsRaycaster physicsRaycaster = raycast.module as OVRPhysicsRaycaster; if (physicsRaycaster) { leftData.position = physicsRaycaster.GetScreenPos(raycast.worldPosition); OVRGazePointer.instance.RequestShow(); OVRGazePointer.instance.SetPosition(raycast.worldPosition, raycast.worldNormal); } // Stick default data values in right and middle slots for compatability // copy the apropriate data into right and middle slots OVRRayPointerEventData rightData; GetPointerData(kMouseRightId, out rightData, true ); CopyFromTo(leftData, rightData); rightData.button = PointerEventData.InputButton.Right; OVRRayPointerEventData middleData; GetPointerData(kMouseMiddleId, out middleData, true ); CopyFromTo(leftData, middleData); middleData.button = PointerEventData.InputButton.Middle; m_MouseState.SetButtonState(PointerEventData.InputButton.Left, GetGazeButtonState(), leftData); m_MouseState.SetButtonState(PointerEventData.InputButton.Right, PointerEventData.FramePressState.NotChanged, rightData); m_MouseState.SetButtonState(PointerEventData.InputButton.Middle, PointerEventData.FramePressState.NotChanged, middleData); return m_MouseState; } /// <summary> /// Get state for pointer which is a pointer moving in world space across the surface of a world space canvas. /// </summary> /// <returns></returns> protected MouseState GetCanvasPointerData() { // Get the OVRRayPointerEventData reference PointerEventData leftData; GetPointerData(kMouseLeftId, out leftData, true ); leftData.Reset(); // Setup default values here. Set position to zero because we don't actually know the pointer // positions. Each canvas knows the position of its canvas pointer. leftData.position = Vector2.zero; leftData.scrollDelta = Input.mouseScrollDelta; leftData.button = PointerEventData.InputButton.Left; if (activeGraphicRaycaster) { // Let the active raycaster find intersections on its canvas activeGraphicRaycaster.RaycastPointer(leftData, m_RaycastResultCache); var raycast = FindFirstRaycast(m_RaycastResultCache); leftData.pointerCurrentRaycast = raycast; m_RaycastResultCache.Clear(); OVRRaycaster ovrRaycaster = raycast.module as OVRRaycaster; if (ovrRaycaster) // raycast may not actually contain a result { // The Unity UI system expects event data to have a screen position // so even though this raycast came from a world space ray we must get a screen // space position for the camera attached to this raycaster for compatability Vector2 position = ovrRaycaster.GetScreenPosition(raycast); leftData.delta = position - leftData.position; leftData.position = position; } } // copy the apropriate data into right and middle slots PointerEventData rightData; GetPointerData(kMouseRightId, out rightData, true ); CopyFromTo(leftData, rightData); rightData.button = PointerEventData.InputButton.Right; PointerEventData middleData; GetPointerData(kMouseMiddleId, out middleData, true ); CopyFromTo(leftData, middleData); middleData.button = PointerEventData.InputButton.Middle; m_MouseState.SetButtonState(PointerEventData.InputButton.Left, StateForMouseButton(0), leftData); m_MouseState.SetButtonState(PointerEventData.InputButton.Right, StateForMouseButton(1), rightData); m_MouseState.SetButtonState(PointerEventData.InputButton.Middle, StateForMouseButton(2), middleData); return m_MouseState; } /// <summary> /// New version of ShouldStartDrag implemented first in PointerInputModule. This version differs in that /// for ray based pointers it makes a decision about whether a drag should start based on the angular change /// the pointer has made so far, as seen from the camera. This also works when the world space ray is /// translated rather than rotated, since the beginning and end of the movement are considered as angle from /// the same point. /// </summary> private bool ShouldStartDrag(PointerEventData pointerEvent) { if (!pointerEvent.useDragThreshold) return true; if (pointerEvent as OVRRayPointerEventData == null) { // Same as original behaviour for canvas based pointers return (pointerEvent.pressPosition - pointerEvent.position).sqrMagnitude >= eventSystem.pixelDragThreshold * eventSystem.pixelDragThreshold; } else { // When it's not a screen space pointer we have to look at the angle it moved rather than the pixels distance // For gaze based pointing screen-space distance moved will always be near 0 Vector3 cameraPos = pointerEvent.pressEventCamera.transform.position; Vector3 pressDir = (pointerEvent.pointerPressRaycast.worldPosition - cameraPos).normalized; Vector3 currentDir = (pointerEvent.pointerCurrentRaycast.worldPosition - cameraPos).normalized; return Vector3.Dot(pressDir, currentDir) < Mathf.Cos(Mathf.Deg2Rad * (angleDragThreshold)); } } /// <summary> /// The purpose of this function is to allow us to switch between using the standard IsPointerMoving /// method for mouse driven pointers, but to always return true when it's a ray based pointer. /// All real-world ray-based input devices are always moving so for simplicity we just return true /// for them. /// /// If PointerEventData.IsPointerMoving was virtual we could just override that in /// OVRRayPointerEventData. /// </summary> /// <param name="pointerEvent"></param> /// <returns></returns> static bool IsPointerMoving(PointerEventData pointerEvent) { OVRRayPointerEventData rayPointerEventData = pointerEvent as OVRRayPointerEventData; if (rayPointerEventData != null) return true; else return pointerEvent.IsPointerMoving(); } /// <summary> /// Exactly the same as the code from PointerInputModule, except that we call our own /// IsPointerMoving. /// /// This would also not be necessary if PointerEventData.IsPointerMoving was virtual /// </summary> /// <param name="pointerEvent"></param> protected override void ProcessDrag(PointerEventData pointerEvent) { bool moving = IsPointerMoving(pointerEvent); if (moving && pointerEvent.pointerDrag != null && !pointerEvent.dragging && ShouldStartDrag(pointerEvent)) { ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.beginDragHandler); pointerEvent.dragging = true; } // Drag notification if (pointerEvent.dragging && moving && pointerEvent.pointerDrag != null) { // Before doing drag we should cancel any pointer down state // And clear selection! if (pointerEvent.pointerPress != pointerEvent.pointerDrag) { ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler); pointerEvent.eligibleForClick = false; pointerEvent.pointerPress = null; pointerEvent.rawPointerPress = null; } ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.dragHandler); } } /// <summary> /// Get state of button corresponding to gaze pointer /// </summary> /// <returns></returns> protected PointerEventData.FramePressState GetGazeButtonState() { var pressed = Input.GetKeyDown(gazeClickKey) || OVRInput.GetDown(joyPadClickButton); var released = Input.GetKeyUp(gazeClickKey) || OVRInput.GetUp(joyPadClickButton); #if UNITY_ANDROID && !UNITY_EDITOR pressed |= Input.GetMouseButtonDown(0); released |= Input.GetMouseButtonUp(0); #endif if (pressed && released) return PointerEventData.FramePressState.PressedAndReleased; if (pressed) return PointerEventData.FramePressState.Pressed; if (released) return PointerEventData.FramePressState.Released; return PointerEventData.FramePressState.NotChanged; } /// <summary> /// Get extra scroll delta from gamepad /// </summary> protected Vector2 GetExtraScrollDelta() { Vector2 scrollDelta = new Vector2(); if (useLeftStickScroll) { float x = OVRInput.Get(OVRInput.Axis2D.PrimaryThumbstick).x; float y = OVRInput.Get(OVRInput.Axis2D.PrimaryThumbstick).y; if (Mathf.Abs(x) < leftStickDeadZone) x = 0; if (Mathf.Abs(y) < leftStickDeadZone) y = 0; scrollDelta = new Vector2 (x,y); } return scrollDelta; } }; }