VR4Medical/ICI/Library/PackageCache/com.unity.xr.interaction.toolkit@42ef3600567b/Runtime/AR/Gestures/GestureTouchesUtility.cs
2025-07-29 13:45:50 +03:00

349 lines
15 KiB
C#

//-----------------------------------------------------------------------
// <copyright file="GestureTouchesUtility.cs" company="Google">
//
// 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.
//
// </copyright>
//-----------------------------------------------------------------------
// 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<string, FieldInfo> s_Fields = new Dictionary<string, FieldInfo>();
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;
}
/// <summary>
/// An adapter struct that wraps a <c>Touch</c> from either the Input System package or the Input Manager.
/// </summary>
/// <seealso cref="Touch"/>
/// <seealso cref="InputSystem.EnhancedTouch.Touch"/>
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;
/// <summary>
/// Unique ID of the touch used to identify it across multiple frames.
/// Not to be confused with <see cref="InputSystem.EnhancedTouch.Finger.index"/>.
/// </summary>
/// <seealso cref="Touch.fingerId"/>
/// <seealso cref="InputSystem.EnhancedTouch.Touch.touchId"/>
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;
}
/// <summary>
/// Gets the <see cref="Touch"/> if constructed with <see cref="CommonTouch(Touch)"/>.
/// </summary>
/// <returns>Returns the <see cref="Touch"/> used to construct this object. Otherwise, throws an exception.</returns>
/// <exception cref="InvalidOperationException">Throws when this object was constructed with an Input System <see cref="InputSystem.EnhancedTouch.Touch"/>.</exception>
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.");
/// <summary>
/// Gets the <see cref="InputSystem.EnhancedTouch.Touch"/> if constructed with <see cref="CommonTouch(InputSystem.EnhancedTouch.Touch)"/>.
/// </summary>
/// <returns>Returns the <see cref="InputSystem.EnhancedTouch.Touch"/> used to construct this object. Otherwise, throws an exception.</returns>
/// <exception cref="InvalidOperationException">Throws when this object was constructed with an Input Manager <see cref="Touch"/>.</exception>
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.");
}
/// <summary>
/// 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.
/// </summary>
static class GestureTouchesUtility
{
public enum TouchInputSource
{
Legacy,
Enhanced,
Mock,
}
const float k_EdgeThresholdInches = 0.1f;
/// <summary>
/// The default source of <c>Touch</c> input this class uses.
/// </summary>
/// <remarks>
/// Defaults to use legacy Input Manager Touch input when the <b>Active Input Handling</b> mode of the Unity project
/// is set to <b>Both</b> for backwards compatibility with existing projects.
/// </remarks>
public static readonly TouchInputSource defaultTouchInputSource =
#if ENABLE_LEGACY_INPUT_MANAGER
TouchInputSource.Legacy;
#else
TouchInputSource.Enhanced;
#endif
public static TouchInputSource touchInputSource { get; set; } = defaultTouchInputSource;
/// <summary>
/// The list of the status of all touches.
/// Does not return a copy in order to avoid allocation.
/// </summary>
public static List<CommonTouch> 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<CommonTouch> s_Touches = new List<CommonTouch>();
internal static readonly List<MockTouch> mockTouches = new List<MockTouch>();
static readonly HashSet<int> s_RetainedFingerIds = new HashSet<int>();
/// <summary>
/// Try to find a touch for a particular finger id.
/// </summary>
/// <param name="fingerId">The finger id to find the touch.</param>
/// <param name="touchOut">The output touch.</param>
/// <returns>True if a touch was found.</returns>
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;
}
/// <summary>
/// Converts Pixels to Inches.
/// </summary>
/// <param name="pixels">The amount to convert in pixels.</param>
/// <returns>The converted amount in inches.</returns>
public static float PixelsToInches(float pixels) => pixels / Screen.dpi;
/// <summary>
/// Converts Inches to Pixels.
/// </summary>
/// <param name="inches">The amount to convert in inches.</param>
/// <returns>The converted amount in pixels.</returns>
public static float InchesToPixels(float inches) => inches * Screen.dpi;
/// <summary>
/// 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.
/// </summary>
/// <param name="touch">The touch to check.</param>
/// <returns>True if the touch is off screen edge.</returns>
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;
}
/// <summary>
/// Performs a Raycast from the camera.
/// </summary>
/// <remarks>
/// Unity uses the 3D physics scene of the camera when performing the ray cast.
/// </remarks>
/// <param name="screenPos">The screen position to perform the ray cast from.</param>
/// <param name="sessionOrigin">The <see cref="XROrigin"/> whose Camera is used for ray casting.</param>
/// <param name="arSessionOrigin">The fallback <see cref="ARSessionOrigin"/> whose Camera is used for ray casting.</param>
/// <param name="result">When this method returns, contains the <see cref="RaycastHit"/> result.</param>
/// <param name="layerMask">The layer mask used for limiting raycast targets.</param>
/// <param name="triggerInteraction">The type of interaction with trigger colliders via raycast.</param>
/// <returns>Returns <see langword="true"/> if an object was hit. Otherwise, returns <see langword="false"/>.</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);
}
/// <summary>
/// Locks a finger Id.
/// </summary>
/// <param name="fingerId">The finger id to lock.</param>
public static void LockFingerId(int fingerId)
{
s_RetainedFingerIds.Add(fingerId);
}
/// <summary>
/// Releases a finger Id.
/// </summary>
/// <param name="fingerId">The finger id to release.</param>
public static void ReleaseFingerId(int fingerId)
{
s_RetainedFingerIds.Remove(fingerId);
}
/// <summary>
/// Returns true if the finger Id is retained.
/// </summary>
/// <param name="fingerId">The finger id to check.</param>
/// <returns>Returns <see langword="true"/> if the finger is retained. Otherwise, returns <see langword="false"/>.</returns>
public static bool IsFingerIdRetained(int fingerId)
{
return s_RetainedFingerIds.Contains(fingerId);
}
}
}
#endif