/**************************************************************************** * Copyright 2019 Nreal Techonology Limited. All rights reserved. * * This file is part of NRSDK. * * https://www.nreal.ai/ * *****************************************************************************/ namespace NRKernal { using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; /// A nr input module. public class NRInputModule : BaseInputModule { /// The processed frame. private int m_processedFrame; /// The raycasters. private static readonly List raycasters = new List(); /// Gets a value indicating whether the active. /// True if active, false if not. public static bool Active { get { return m_Instance != null; } } /// Gets the screen center point. /// The screen center point. public static Vector2 ScreenCenterPoint { get { return new Vector2(Screen.width * 0.5f, Screen.height * 0.5f); } } /// True if is application quitting, false if not. private static bool isApplicationQuitting = false; /// The instance. private static NRInputModule m_Instance; /// Gets the instance. /// The instance. public static NRInputModule Instance { get { Initialize(); return m_Instance; } } /// Update the internal state of the Module. public override void UpdateModule() { Initialize(); if (isActiveAndEnabled && EventSystem.current.currentInputModule != this) { ProcessRaycast(); } } /// Process the raycast. protected virtual void ProcessRaycast() { if (m_processedFrame == Time.frameCount) return; m_processedFrame = Time.frameCount; RaycastAll(); } /// Raycast all. private void RaycastAll() { for (int i = 0; i < raycasters.Count; i++) { var raycaster = raycasters[i]; if (raycaster == null) continue; raycaster.Raycast(); var result = raycaster.FirstRaycastResult(); var scrollDelta = raycaster.GetScrollDelta(); var raycasterPos = raycaster.BreakPoints[0]; var raycasterRot = raycaster.transform.rotation; var hoverEventData = raycaster.HoverEventData; if (hoverEventData == null) continue; hoverEventData.Reset(); hoverEventData.delta = Vector2.zero; hoverEventData.scrollDelta = scrollDelta; hoverEventData.position = ScreenCenterPoint; hoverEventData.pointerCurrentRaycast = result; hoverEventData.position3DDelta = raycasterPos - hoverEventData.position3D; hoverEventData.position3D = raycasterPos; hoverEventData.rotationDelta = Quaternion.Inverse(hoverEventData.rotation) * raycasterRot; hoverEventData.rotation = raycasterRot; for (int j = 0, jmax = raycaster.ButtonEventDataList.Count; j < jmax; ++j) { var buttonEventData = raycaster.ButtonEventDataList[j]; if (buttonEventData == null || buttonEventData == hoverEventData) continue; buttonEventData.Reset(); buttonEventData.delta = Vector2.zero; buttonEventData.scrollDelta = scrollDelta; buttonEventData.position = ScreenCenterPoint; buttonEventData.pointerCurrentRaycast = result; buttonEventData.position3DDelta = hoverEventData.position3DDelta; buttonEventData.position3D = hoverEventData.position3D; buttonEventData.rotationDelta = hoverEventData.rotationDelta; buttonEventData.rotation = hoverEventData.rotation; } ProcessPress(hoverEventData); ProcessMove(hoverEventData); ProcessDrag(hoverEventData); for (int j = 1, jmax = raycaster.ButtonEventDataList.Count; j < jmax; ++j) { var buttonEventData = raycaster.ButtonEventDataList[j]; if (buttonEventData == null || buttonEventData == hoverEventData) continue; buttonEventData.pointerEnter = hoverEventData.pointerEnter; ProcessPress(buttonEventData); ProcessDrag(buttonEventData); } } } /// Initializes this object. public static void Initialize() { if (Active || isApplicationQuitting) return; var instances = FindObjectsOfType(); if (instances.Length > 0) { m_Instance = instances[0]; if (instances.Length > 1) { NRDebugger.Warning("Multiple NRInputModule not supported!"); } } if (!Active) { EventSystem eventSystem = EventSystem.current; if (eventSystem == null) { eventSystem = FindObjectOfType(); } if (eventSystem == null) { eventSystem = new GameObject("[EventSystem]").AddComponent(); } if (eventSystem == null) { NRDebugger.Warning("EventSystem not found or create fail!"); return; } m_Instance = eventSystem.gameObject.AddComponent(); DontDestroyOnLoad(eventSystem.gameObject); } } /// Process the current tick for the module. public override void Process() { Initialize(); if (isActiveAndEnabled) { ProcessRaycast(); } } /// Executes the 'application quit' action. private void OnApplicationQuit() { isApplicationQuitting = true; } /// Adds a raycaster. /// The raycaster. public static void AddRaycaster(NRPointerRaycaster raycaster) { if (raycaster == null) return; Initialize(); raycasters.Add(raycaster); } /// Removes the raycaster described by raycaster. /// The raycaster. public static void RemoveRaycaster(NRPointerRaycaster raycaster) { if (m_Instance) { m_Instance.ProcessRaycast(); } raycasters.Remove(raycaster); } /// The default raycast comparer. public static readonly Comparison defaultRaycastComparer = RaycastComparer; /// Raycast comparer. /// The left hand side. /// The right hand side. /// An int. private static int RaycastComparer(RaycastResult lhs, RaycastResult rhs) { if (lhs.module != rhs.module) { if (lhs.module.eventCamera != null && rhs.module.eventCamera != null && lhs.module.eventCamera.depth != rhs.module.eventCamera.depth) { if (lhs.module.eventCamera.depth < rhs.module.eventCamera.depth) { return 1; } if (lhs.module.eventCamera.depth == rhs.module.eventCamera.depth) { return 0; } return -1; } if (lhs.module.sortOrderPriority != rhs.module.sortOrderPriority) { return rhs.module.sortOrderPriority.CompareTo(lhs.module.sortOrderPriority); } if (lhs.module.renderOrderPriority != rhs.module.renderOrderPriority) { return rhs.module.renderOrderPriority.CompareTo(lhs.module.renderOrderPriority); } } if (lhs.sortingLayer != rhs.sortingLayer) { var rid = SortingLayer.GetLayerValueFromID(rhs.sortingLayer); var lid = SortingLayer.GetLayerValueFromID(lhs.sortingLayer); return rid.CompareTo(lid); } if (lhs.sortingOrder != rhs.sortingOrder) { return rhs.sortingOrder.CompareTo(lhs.sortingOrder); } if (!Mathf.Approximately(lhs.distance, rhs.distance)) { return lhs.distance.CompareTo(rhs.distance); } if (lhs.depth != rhs.depth) { return rhs.depth.CompareTo(lhs.depth); } return lhs.index.CompareTo(rhs.index); } /// Process the move described by eventData. /// Information describing the event. protected virtual void ProcessMove(PointerEventData eventData) { var hoverGO = eventData.pointerCurrentRaycast.gameObject; if (eventData.pointerEnter != hoverGO) { HandlePointerExitAndEnter(eventData, hoverGO); } } /// Process the press described by eventData. /// Information describing the event. protected virtual void ProcessPress(NRPointerEventData eventData) { if (eventData.GetPress()) { if (!eventData.pressPrecessed) { ProcessPressDown(eventData); } HandlePressExitAndEnter(eventData, eventData.pointerCurrentRaycast.gameObject); } else if (eventData.pressPrecessed) { ProcessPressUp(eventData); HandlePressExitAndEnter(eventData, null); } } /// Process the press down described by eventData. /// Information describing the event. protected void ProcessPressDown(NRPointerEventData eventData) { var currentOverGo = eventData.pointerCurrentRaycast.gameObject; eventData.pressPrecessed = true; eventData.eligibleForClick = true; eventData.delta = Vector2.zero; eventData.dragging = false; eventData.useDragThreshold = true; eventData.pressPosition = eventData.position; eventData.pressPosition3D = eventData.position3D; eventData.pressRotation = eventData.rotation; eventData.pressDistance = eventData.pointerCurrentRaycast.distance; eventData.pointerPressRaycast = eventData.pointerCurrentRaycast; DeselectIfSelectionChanged(currentOverGo, eventData); var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, eventData, ExecuteEvents.pointerDownHandler); if (newPressed == null) { newPressed = ExecuteEvents.GetEventHandler(currentOverGo); } var time = Time.unscaledTime; if (newPressed == eventData.lastPress) { if (eventData.raycaster != null && time < (eventData.clickTime + NRInput.ClickInterval)) { ++eventData.clickCount; } else { eventData.clickCount = 1; } eventData.clickTime = time; } else { eventData.clickCount = 1; } eventData.pointerPress = newPressed; eventData.rawPointerPress = currentOverGo; eventData.clickTime = time; eventData.pointerDrag = ExecuteEvents.GetEventHandler(currentOverGo); if (eventData.pointerDrag != null) { ExecuteEvents.Execute(eventData.pointerDrag, eventData, ExecuteEvents.initializePotentialDrag); } } /// Process the press up described by eventData. /// Information describing the event. protected void ProcessPressUp(NRPointerEventData eventData) { var currentOverGo = eventData.pointerCurrentRaycast.gameObject; ExecuteEvents.Execute(eventData.pointerPress, eventData, ExecuteEvents.pointerUpHandler); var pointerUpHandler = ExecuteEvents.GetEventHandler(currentOverGo); if (eventData.pointerPress == pointerUpHandler && eventData.eligibleForClick) { ExecuteEvents.Execute(eventData.pointerPress, eventData, ExecuteEvents.pointerClickHandler); } else if (eventData.pointerDrag != null && eventData.dragging) { ExecuteEvents.ExecuteHierarchy(currentOverGo, eventData, ExecuteEvents.dropHandler); } eventData.pressPrecessed = false; eventData.eligibleForClick = false; eventData.pointerPress = null; eventData.rawPointerPress = null; if (eventData.pointerDrag != null && eventData.dragging) { ExecuteEvents.Execute(eventData.pointerDrag, eventData, ExecuteEvents.endDragHandler); } eventData.dragging = false; eventData.pointerDrag = null; if (currentOverGo != eventData.pointerEnter) { HandlePointerExitAndEnter(eventData, null); HandlePointerExitAndEnter(eventData, currentOverGo); } } /// Determine if we should start drag. /// Information describing the event. /// True if it succeeds, false if it fails. protected bool ShouldStartDrag(NRPointerEventData eventData) { if (!eventData.useDragThreshold || eventData.raycaster == null) { return true; } var currentPos = eventData.position3D + (eventData.rotation * Vector3.forward) * eventData.pressDistance; var pressPos = eventData.pressPosition3D + (eventData.pressRotation * Vector3.forward) * eventData.pressDistance; var threshold = NRInput.DragThreshold; return (currentPos - pressPos).sqrMagnitude >= threshold * threshold; } /// Process the drag described by eventData. /// Information describing the event. protected void ProcessDrag(NRPointerEventData eventData) { var moving = !Mathf.Approximately(eventData.position3DDelta.sqrMagnitude, 0f) || !Mathf.Approximately(Quaternion.Angle(Quaternion.identity, eventData.rotationDelta), 0f); if (moving && eventData.pointerDrag != null && !eventData.dragging && ShouldStartDrag(eventData)) { ExecuteEvents.Execute(eventData.pointerDrag, eventData, ExecuteEvents.beginDragHandler); eventData.dragging = true; } if (eventData.dragging && moving && eventData.pointerDrag != null) { if (eventData.pointerPress != eventData.pointerDrag) { ExecuteEvents.Execute(eventData.pointerPress, eventData, ExecuteEvents.pointerUpHandler); eventData.eligibleForClick = false; eventData.pointerPress = null; eventData.rawPointerPress = null; } ExecuteEvents.Execute(eventData.pointerDrag, eventData, ExecuteEvents.dragHandler); } } /// Handles the press exit and enter. /// Information describing the event. /// The new enter target. protected static void HandlePressExitAndEnter(NRPointerEventData eventData, GameObject newEnterTarget) { if (eventData.pressEnter == newEnterTarget) return; var oldTarget = eventData.pressEnter == null ? null : eventData.pressEnter.transform; var newTarget = newEnterTarget == null ? null : newEnterTarget.transform; var commonRoot = default(Transform); for (var t = oldTarget; t != null; t = t.parent) { if (newTarget != null && newTarget.IsChildOf(t)) { commonRoot = t; break; } else { ExecuteEvents.Execute(t.gameObject, eventData, NRExecutePointerEvents.PressExitHandler); } } eventData.pressEnter = newEnterTarget; for (var t = newTarget; t != commonRoot; t = t.parent) { ExecuteEvents.Execute(t.gameObject, eventData, NRExecutePointerEvents.PressEnterHandler); } } /// Deselect if selection changed. /// The current over go. /// The pointer event. protected void DeselectIfSelectionChanged(GameObject currentOverGo, BaseEventData pointerEvent) { var selectHandlerGO = ExecuteEvents.GetEventHandler(currentOverGo); if (eventSystem != null && selectHandlerGO != eventSystem.currentSelectedGameObject) { eventSystem.SetSelectedGameObject(null, pointerEvent); } } } }