//----------------------------------------------------------------------- // // // Copyright 2018 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // 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. // // //----------------------------------------------------------------------- // Modifications copyright © 2020 Unity Technologies ApS #if AR_FOUNDATION_PRESENT || PACKAGE_DOCS_GENERATION using System; using System.Collections.Generic; using System.Reflection; using UnityEngine.XR.ARFoundation; using Unity.XR.CoreUtils; namespace UnityEngine.XR.Interaction.Toolkit.AR { class MockTouch { public float deltaTime { get => ((Touch)m_Touch).deltaTime; set => s_Fields["m_TimeDelta"].SetValue(m_Touch, value); } public int tapCount { get => ((Touch)m_Touch).tapCount; set => s_Fields["m_TapCount"].SetValue(m_Touch, value); } public TouchPhase phase { get => ((Touch)m_Touch).phase; set => s_Fields["m_Phase"].SetValue(m_Touch, value); } public Vector2 deltaPosition { get => ((Touch)m_Touch).deltaPosition; set => s_Fields["m_PositionDelta"].SetValue(m_Touch, value); } public int fingerId { get => ((Touch)m_Touch).fingerId; set => s_Fields["m_FingerId"].SetValue(m_Touch, value); } public Vector2 position { get => ((Touch)m_Touch).position; set => s_Fields["m_Position"].SetValue(m_Touch, value); } public Vector2 rawPosition { get => ((Touch)m_Touch).rawPosition; set => s_Fields["m_RawPosition"].SetValue(m_Touch, value); } static readonly Dictionary s_Fields = new Dictionary(); readonly object m_Touch = new Touch(); static MockTouch() { foreach (var f in typeof(Touch).GetFields(BindingFlags.Instance | BindingFlags.NonPublic)) s_Fields.Add(f.Name, f); } public Touch ToTouch() => (Touch)m_Touch; } /// /// An adapter struct that wraps a Touch from either the Input System package or the Input Manager. /// /// /// readonly struct CommonTouch { public float deltaTime => m_IsEnhancedTouch ? (float)(m_EnhancedTouch.time - m_EnhancedTouch.startTime) : m_Touch.deltaTime; public int tapCount => m_IsEnhancedTouch ? m_EnhancedTouch.tapCount : m_Touch.tapCount; public bool isPhaseBegan => m_IsEnhancedTouch ? m_EnhancedTouch.phase == InputSystem.TouchPhase.Began : m_Touch.phase == TouchPhase.Began; public bool isPhaseMoved => m_IsEnhancedTouch ? m_EnhancedTouch.phase == InputSystem.TouchPhase.Moved : m_Touch.phase == TouchPhase.Moved; public bool isPhaseStationary => m_IsEnhancedTouch ? m_EnhancedTouch.phase == InputSystem.TouchPhase.Stationary : m_Touch.phase == TouchPhase.Stationary; public bool isPhaseEnded => m_IsEnhancedTouch ? m_EnhancedTouch.phase == InputSystem.TouchPhase.Ended : m_Touch.phase == TouchPhase.Ended; public bool isPhaseCanceled => m_IsEnhancedTouch ? m_EnhancedTouch.phase == InputSystem.TouchPhase.Canceled : m_Touch.phase == TouchPhase.Canceled; public Vector2 deltaPosition => m_IsEnhancedTouch ? m_EnhancedTouch.delta : m_Touch.deltaPosition; /// /// Unique ID of the touch used to identify it across multiple frames. /// Not to be confused with . /// /// /// public int fingerId => m_IsEnhancedTouch ? m_EnhancedTouch.touchId : m_Touch.fingerId; public Vector2 position => m_IsEnhancedTouch ? m_EnhancedTouch.screenPosition : m_Touch.position; readonly Touch m_Touch; readonly InputSystem.EnhancedTouch.Touch m_EnhancedTouch; readonly bool m_IsEnhancedTouch; public CommonTouch(Touch touch) { m_Touch = touch; m_EnhancedTouch = default; m_IsEnhancedTouch = false; } public CommonTouch(InputSystem.EnhancedTouch.Touch touch) { m_Touch = default; m_EnhancedTouch = touch; m_IsEnhancedTouch = true; } /// /// Gets the if constructed with . /// /// Returns the used to construct this object. Otherwise, throws an exception. /// Throws when this object was constructed with an Input System . public Touch GetTouch() => !m_IsEnhancedTouch ? m_Touch : throw new InvalidOperationException($"Cannot convert to {typeof(Touch).FullName} since this was sourced from the Input System package."); /// /// Gets the if constructed with . /// /// Returns the used to construct this object. Otherwise, throws an exception. /// Throws when this object was constructed with an Input Manager . public InputSystem.EnhancedTouch.Touch GetEnhancedTouch() => m_IsEnhancedTouch ? m_EnhancedTouch : throw new InvalidOperationException($"Cannot convert to {typeof(InputSystem.EnhancedTouch.Touch).FullName} since this was sourced from Input Manager Input."); } /// /// Singleton used by Gestures and GestureRecognizers to interact with touch input. /// /// 1. Makes it easy to find touches by fingerId. /// 2. Allows Gestures to Lock/Release fingerIds. /// 3. Wraps Input.Touches so that it works both in editor and on device. /// 4. Provides helper functions for converting touch coordinates /// and performing ray casts based on touches. /// static class GestureTouchesUtility { public enum TouchInputSource { Legacy, Enhanced, Mock, } const float k_EdgeThresholdInches = 0.1f; /// /// The default source of Touch input this class uses. /// /// /// Defaults to use legacy Input Manager Touch input when the Active Input Handling mode of the Unity project /// is set to Both for backwards compatibility with existing projects. /// public static readonly TouchInputSource defaultTouchInputSource = #if ENABLE_LEGACY_INPUT_MANAGER TouchInputSource.Legacy; #else TouchInputSource.Enhanced; #endif public static TouchInputSource touchInputSource { get; set; } = defaultTouchInputSource; /// /// The list of the status of all touches. /// Does not return a copy in order to avoid allocation. /// public static List touches { get { s_Touches.Clear(); switch (touchInputSource) { case TouchInputSource.Legacy: for (int index = 0, touchCount = Input.touchCount; index < touchCount; ++index) s_Touches.Add(new CommonTouch(Input.GetTouch(index))); break; case TouchInputSource.Enhanced: // ReSharper disable once ForCanBeConvertedToForeach -- Would produce garbage, ReadOnlyArray does not use a struct for the enumerator for (var index = 0; index < InputSystem.EnhancedTouch.Touch.activeTouches.Count; ++index) { var touch = InputSystem.EnhancedTouch.Touch.activeTouches[index]; s_Touches.Add(new CommonTouch(touch)); } break; case TouchInputSource.Mock: foreach (var touch in mockTouches) s_Touches.Add(new CommonTouch(touch.ToTouch())); break; } return s_Touches; } } static readonly List s_Touches = new List(); internal static readonly List mockTouches = new List(); static readonly HashSet s_RetainedFingerIds = new HashSet(); /// /// Try to find a touch for a particular finger id. /// /// The finger id to find the touch. /// The output touch. /// True if a touch was found. public static bool TryFindTouch(int fingerId, out CommonTouch touchOut) { foreach (var touch in touches) { if (touch.fingerId == fingerId) { touchOut = touch; return true; } } touchOut = default; return false; } /// /// Converts Pixels to Inches. /// /// The amount to convert in pixels. /// The converted amount in inches. public static float PixelsToInches(float pixels) => pixels / Screen.dpi; /// /// Converts Inches to Pixels. /// /// The amount to convert in inches. /// The converted amount in pixels. public static float InchesToPixels(float inches) => inches * Screen.dpi; /// /// Used to determine if a touch is off the edge of the screen based on some slop. /// Useful to prevent accidental touches from simply holding the device from causing /// confusing behavior. /// /// The touch to check. /// True if the touch is off screen edge. public static bool IsTouchOffScreenEdge(CommonTouch touch) { var slopPixels = InchesToPixels(k_EdgeThresholdInches); var result = touch.position.x <= slopPixels; result |= touch.position.y <= slopPixels; result |= touch.position.x >= Screen.width - slopPixels; result |= touch.position.y >= Screen.height - slopPixels; return result; } /// /// Performs a Raycast from the camera. /// /// /// Unity uses the 3D physics scene of the camera when performing the ray cast. /// /// The screen position to perform the ray cast from. /// The whose Camera is used for ray casting. /// The fallback whose Camera is used for ray casting. /// When this method returns, contains the result. /// The layer mask used for limiting raycast targets. /// The type of interaction with trigger colliders via raycast. /// Returns if an object was hit. Otherwise, returns . #pragma warning disable CS0618 // ARSessionOrigin is deprecated in 5.0, but kept to support older AR Foundation versions public static bool RaycastFromCamera(Vector2 screenPos, XROrigin sessionOrigin, ARSessionOrigin arSessionOrigin, out RaycastHit result, LayerMask layerMask = default, QueryTriggerInteraction triggerInteraction = QueryTriggerInteraction.Ignore) #pragma warning restore CS0618 { // The ARSessionOrigin parameter will eventually be removed. This class is internal so no need for overloaded. var camera = sessionOrigin != null ? sessionOrigin.Camera #pragma warning disable CS0618 // ARSessionOrigin will eventually be removed, but until then use the oldest access method : (arSessionOrigin != null ? arSessionOrigin.camera : Camera.main); #pragma warning restore CS0618 if (camera == null) { result = default; return false; } var ray = camera.ScreenPointToRay(screenPos); return camera.gameObject.scene.GetPhysicsScene().Raycast(ray.origin, ray.direction, out result, Mathf.Infinity, layerMask, triggerInteraction); } /// /// Locks a finger Id. /// /// The finger id to lock. public static void LockFingerId(int fingerId) { s_RetainedFingerIds.Add(fingerId); } /// /// Releases a finger Id. /// /// The finger id to release. public static void ReleaseFingerId(int fingerId) { s_RetainedFingerIds.Remove(fingerId); } /// /// Returns true if the finger Id is retained. /// /// The finger id to check. /// Returns if the finger is retained. Otherwise, returns . public static bool IsFingerIdRetained(int fingerId) { return s_RetainedFingerIds.Contains(fingerId); } } } #endif