//----------------------------------------------------------------------- // // // 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 using System; #if !AR_FOUNDATION_PRESENT && !PACKAGE_DOCS_GENERATION // Stub class definition used to fool version defines that this MonoScript exists (fixed in 19.3) namespace UnityEngine.XR.Interaction.Toolkit.AR { /// /// Base class that manipulates an object via a gesture. /// [Obsolete("ARBaseGestureInteractable has been deprecated.")] public class ARBaseGestureInteractable { } } #else using System.Collections.Generic; using Unity.XR.CoreUtils; using UnityEngine.Assertions; using UnityEngine.EventSystems; using UnityEngine.XR.ARFoundation; using UnityEngine.XR.Interaction.Toolkit.Interactables; using UnityEngine.XR.Interaction.Toolkit.Interactors; using UnityEngine.XR.Interaction.Toolkit.Utilities; namespace UnityEngine.XR.Interaction.Toolkit.AR { /// /// Base class that manipulates an object via a gesture. /// [Obsolete("ARBaseGestureInteractable has been deprecated.")] public abstract class ARBaseGestureInteractable : XRBaseInteractable { #pragma warning disable CS0618 // ARSessionOrigin is deprecated in 5.0, but kept to support older AR Foundation versions [SerializeField] ARSessionOrigin m_ARSessionOrigin; #pragma warning restore CS0618 /// /// The ARSessionOrigin /// that this Interactable will use (such as to get the [Camera](xref:UnityEngine.Camera) /// or to transform from Session space). Will find one if . /// [Obsolete("arSessionOrigin is marked for deprecation and will be removed in a future version. Please use xrOrigin instead.")] public ARSessionOrigin arSessionOrigin { get => m_ARSessionOrigin; set => m_ARSessionOrigin = value; } [SerializeField] XROrigin m_XROrigin; /// /// The /// that this Interactable will use (such as to get the [Camera](xref:UnityEngine.Camera) /// or to transform from Session space). Will find one if . /// public XROrigin xrOrigin { get => m_XROrigin; set => m_XROrigin = value; } [SerializeField] bool m_ExcludeUITouches = true; /// /// When , will exclude touches that are over UI. /// Used to make screen space canvas elements block touches from hitting planes behind it. /// /// public bool excludeUITouches { get => m_ExcludeUITouches; set => m_ExcludeUITouches = value; } /// /// The that this Interactable listens /// to for gestures when connected. /// /// /// protected ARGestureInteractor gestureInteractor { get; private set; } bool m_IsManipulating; /// /// Temporary, reusable list of registered Interactors. /// static readonly List s_Interactors = new List(); /// protected override void Awake() { base.Awake(); FindXROrigin(); FindARSessionOrigin(); } /// protected override void OnEnable() { base.OnEnable(); FindXROrigin(); FindARSessionOrigin(); } /// public override bool IsHoverableBy(IXRHoverInteractor interactor) => interactor is ARGestureInteractor; /// public override bool IsSelectableBy(IXRSelectInteractor interactor) => false; /// /// Determines if the manipulation can start for the given gesture. /// /// The current gesture. /// Returns if the manipulation can start. Otherwise, returns . protected virtual bool CanStartManipulationForGesture(DragGesture gesture) => false; /// /// Determines if the manipulation can start for the given gesture. /// /// The current gesture. /// Returns if the manipulation can start. Otherwise, returns . protected virtual bool CanStartManipulationForGesture(PinchGesture gesture) => false; /// /// Determines if the manipulation can start for the given gesture. /// /// The current gesture. /// Returns if the manipulation can start. Otherwise, returns . protected virtual bool CanStartManipulationForGesture(TapGesture gesture) => false; /// /// Determines if the manipulation can start for the given gesture. /// /// The current gesture. /// Returns if the manipulation can start. Otherwise, returns . protected virtual bool CanStartManipulationForGesture(TwistGesture gesture) => false; /// /// Determines if the manipulation can start for the given gesture. /// /// The current gesture. /// Returns if the manipulation can start. Otherwise, returns . protected virtual bool CanStartManipulationForGesture(TwoFingerDragGesture gesture) => false; /// /// Unity calls this method automatically when the manipulation starts. /// /// The current gesture. /// protected virtual void OnStartManipulation(DragGesture gesture) { } /// /// Unity calls this method automatically when the manipulation starts. /// /// The current gesture. /// protected virtual void OnStartManipulation(PinchGesture gesture) { } /// /// Unity calls this method automatically when the manipulation starts. /// /// The current gesture. /// protected virtual void OnStartManipulation(TapGesture gesture) { } /// /// Unity calls this method automatically when the manipulation starts. /// /// The current gesture. /// protected virtual void OnStartManipulation(TwistGesture gesture) { } /// /// Unity calls this method automatically when the manipulation starts. /// /// The current gesture. /// protected virtual void OnStartManipulation(TwoFingerDragGesture gesture) { } /// /// Unity calls this method automatically when the manipulation continues. /// /// The current gesture. /// protected virtual void OnContinueManipulation(DragGesture gesture) { } /// /// Unity calls this method automatically when the manipulation continues. /// /// The current gesture. /// protected virtual void OnContinueManipulation(PinchGesture gesture) { } /// /// Unity calls this method automatically when the manipulation continues. /// /// The current gesture. /// protected virtual void OnContinueManipulation(TapGesture gesture) { } /// /// Unity calls this method automatically when the manipulation continues. /// /// The current gesture. /// protected virtual void OnContinueManipulation(TwistGesture gesture) { } /// /// Unity calls this method automatically when the manipulation continues. /// /// The current gesture. /// protected virtual void OnContinueManipulation(TwoFingerDragGesture gesture) { } /// /// Unity calls this method automatically when the manipulation ends. /// /// The current gesture. /// protected virtual void OnEndManipulation(DragGesture gesture) { } /// /// Unity calls this method automatically when the manipulation ends. /// /// The current gesture. /// protected virtual void OnEndManipulation(PinchGesture gesture) { } /// /// Unity calls this method automatically when the manipulation ends. /// /// The current gesture. /// protected virtual void OnEndManipulation(TapGesture gesture) { } /// /// Unity calls this method automatically when the manipulation ends. /// /// The current gesture. /// protected virtual void OnEndManipulation(TwistGesture gesture) { } /// /// Unity calls this method automatically when the manipulation ends. /// /// The current gesture. /// protected virtual void OnEndManipulation(TwoFingerDragGesture gesture) { } /// /// Determines if the is selecting the /// this Interactable is attached to. /// /// Returns if the Gesture Interactor /// is selecting the this Interactable is attached /// to. Otherwise, returns . protected virtual bool IsGameObjectSelected() { if (gestureInteractor == null || !gestureInteractor.hasSelection) return false; foreach (var interactable in gestureInteractor.interactablesSelected) { if (interactable is Component interactableComponent && interactableComponent.gameObject == gameObject) return true; } return false; } /// /// Connect an 's gestures to this interactable. /// protected virtual void ConnectGestureInteractor() { if (gestureInteractor == null) return; if (gestureInteractor.dragGestureRecognizer != null) gestureInteractor.dragGestureRecognizer.onGestureStarted += OnGestureStarted; if (gestureInteractor.pinchGestureRecognizer != null) gestureInteractor.pinchGestureRecognizer.onGestureStarted += OnGestureStarted; if (gestureInteractor.tapGestureRecognizer != null) gestureInteractor.tapGestureRecognizer.onGestureStarted += OnGestureStarted; if (gestureInteractor.twistGestureRecognizer != null) gestureInteractor.twistGestureRecognizer.onGestureStarted += OnGestureStarted; if (gestureInteractor.twoFingerDragGestureRecognizer != null) gestureInteractor.twoFingerDragGestureRecognizer.onGestureStarted += OnGestureStarted; } /// /// Disconnect an 's gestures from this interactable. /// protected virtual void DisconnectGestureInteractor() { if (gestureInteractor == null) return; if (gestureInteractor.dragGestureRecognizer != null) gestureInteractor.dragGestureRecognizer.onGestureStarted -= OnGestureStarted; if (gestureInteractor.pinchGestureRecognizer != null) gestureInteractor.pinchGestureRecognizer.onGestureStarted -= OnGestureStarted; if (gestureInteractor.tapGestureRecognizer != null) gestureInteractor.tapGestureRecognizer.onGestureStarted -= OnGestureStarted; if (gestureInteractor.twistGestureRecognizer != null) gestureInteractor.twistGestureRecognizer.onGestureStarted -= OnGestureStarted; if (gestureInteractor.twoFingerDragGestureRecognizer != null) gestureInteractor.twoFingerDragGestureRecognizer.onGestureStarted -= OnGestureStarted; } /// protected override void OnRegistered(InteractableRegisteredEventArgs args) { base.OnRegistered(args); FindAndConnectGestureInteractor(args.manager); } /// protected override void OnUnregistered(InteractableUnregisteredEventArgs args) { base.OnUnregistered(args); if (gestureInteractor != null) { DisconnectGestureInteractor(); gestureInteractor.unregistered -= OnGestureInteractorUnregistered; gestureInteractor = null; } else { args.manager.interactorRegistered -= OnInteractorRegistered; } } void FindAndConnectGestureInteractor(XRInteractionManager manager) { // Find the Gesture Interactor registered to the same Interaction Manager, // warning if there is more than one. To simplify the API, this Interactable // can only listen to one at a time. // If the Gesture Interactor exists, need to handle it being unregistered. // Otherwise, listen on the Interaction Manager until one is registered. gestureInteractor = GetGestureInteractor(manager); if (gestureInteractor != null) { ConnectGestureInteractor(); gestureInteractor.unregistered += OnGestureInteractorUnregistered; } else { manager.interactorRegistered += OnInteractorRegistered; } } void OnGestureInteractorUnregistered(InteractorUnregisteredEventArgs args) { Assert.AreEqual(gestureInteractor, args.interactorObject as ARGestureInteractor); DisconnectGestureInteractor(); gestureInteractor.unregistered -= OnGestureInteractorUnregistered; gestureInteractor = null; // Try to find another or start listening until one is registered FindAndConnectGestureInteractor(args.manager); } void OnInteractorRegistered(InteractorRegisteredEventArgs args) { Assert.IsNull(gestureInteractor); if (args.interactorObject is ARGestureInteractor registeredGestureInteractor) { gestureInteractor = registeredGestureInteractor; ConnectGestureInteractor(); gestureInteractor.unregistered += OnGestureInteractorUnregistered; args.manager.interactorRegistered -= OnInteractorRegistered; } } ARGestureInteractor GetGestureInteractor(XRInteractionManager manager) { ARGestureInteractor result = null; var numRegisteredGestureInteractors = 0; manager.GetRegisteredInteractors(s_Interactors); foreach (var interactor in s_Interactors) { if (interactor is ARGestureInteractor registeredGestureInteractor) { result = registeredGestureInteractor; ++numRegisteredGestureInteractors; } } s_Interactors.Clear(); if (numRegisteredGestureInteractors > 1) { Debug.LogWarning($"More than one {nameof(ARGestureInteractor)} is registered with {manager}," + $" but only one can be listened to at a time. Choosing to listen to {result}.", this); } return result; } void FindXROrigin() { if (m_XROrigin == null) ComponentLocatorUtility.TryFindComponent(out m_XROrigin); } void FindARSessionOrigin() { #pragma warning disable CS0618 // ARSessionOrigin is deprecated in 5.0, but kept to support older AR Foundation versions if (m_ARSessionOrigin == null) ComponentLocatorUtility.TryFindComponent(out m_ARSessionOrigin); #pragma warning restore CS0618 } /// /// Determines if the manipulation can start for the given gesture. /// /// The gesture type. /// The current gesture. /// Returns if the manipulation can /// start. Otherwise, returns . // TODO Consider making this protected virtual and deprecating non-generic methods bool CanStartManipulationForGesture(Gesture gesture) where T : Gesture { switch (gesture) { case DragGesture dragGesture: return CanStartManipulationForGesture(dragGesture); case PinchGesture pinchGesture: return CanStartManipulationForGesture(pinchGesture); case TapGesture tapGesture: return CanStartManipulationForGesture(tapGesture); case TwistGesture twistGesture: return CanStartManipulationForGesture(twistGesture); case TwoFingerDragGesture twoFingerDragGesture: return CanStartManipulationForGesture(twoFingerDragGesture); } return false; } /// /// Unity calls this method automatically when the manipulation starts. /// /// The gesture type. /// The current gesture. /// // TODO Consider making this protected virtual and deprecating non-generic methods void OnStartManipulation(Gesture gesture) where T : Gesture { switch (gesture) { case DragGesture dragGesture: OnStartManipulation(dragGesture); break; case PinchGesture pinchGesture: OnStartManipulation(pinchGesture); break; case TapGesture tapGesture: OnStartManipulation(tapGesture); break; case TwistGesture twistGesture: OnStartManipulation(twistGesture); break; case TwoFingerDragGesture twoFingerDragGesture: OnStartManipulation(twoFingerDragGesture); break; } } /// /// Unity calls this method automatically when the manipulation continues. /// /// The gesture type. /// The current gesture. /// // TODO Consider making this protected virtual and deprecating non-generic methods void OnContinueManipulation(Gesture gesture) where T : Gesture { switch (gesture) { case DragGesture dragGesture: OnContinueManipulation(dragGesture); break; case PinchGesture pinchGesture: OnContinueManipulation(pinchGesture); break; case TapGesture tapGesture: OnContinueManipulation(tapGesture); break; case TwistGesture twistGesture: OnContinueManipulation(twistGesture); break; case TwoFingerDragGesture twoFingerDragGesture: OnContinueManipulation(twoFingerDragGesture); break; } } void OnGestureStarted(Gesture gesture) where T : Gesture { if (m_IsManipulating) return; if (m_ExcludeUITouches && IsGestureBlockedByUI(gesture)) return; if (CanStartManipulationForGesture(gesture)) { m_IsManipulating = true; gesture.onUpdated += OnUpdated; gesture.onFinished += OnFinished; OnStartManipulation(gesture); } } void OnUpdated(Gesture gesture) where T : Gesture { if (!m_IsManipulating) return; // Can only transform selected Items. if (!IsGameObjectSelected()) { m_IsManipulating = false; // Contents of the removed OnEndManipulation(Gesture) were copied here to avoid an IL2CPP runtime crash switch (gesture) { case DragGesture dragGesture: OnEndManipulation(dragGesture); break; case PinchGesture pinchGesture: OnEndManipulation(pinchGesture); break; case TapGesture tapGesture: OnEndManipulation(tapGesture); break; case TwistGesture twistGesture: OnEndManipulation(twistGesture); break; case TwoFingerDragGesture twoFingerDragGesture: OnEndManipulation(twoFingerDragGesture); break; } return; } OnContinueManipulation(gesture); } void OnFinished(Gesture gesture) where T : Gesture { m_IsManipulating = false; // Contents of the removed OnEndManipulation(Gesture) were copied here to avoid an IL2CPP runtime crash switch (gesture) { case DragGesture dragGesture: OnEndManipulation(dragGesture); break; case PinchGesture pinchGesture: OnEndManipulation(pinchGesture); break; case TapGesture tapGesture: OnEndManipulation(tapGesture); break; case TwistGesture twistGesture: OnEndManipulation(twistGesture); break; case TwoFingerDragGesture twoFingerDragGesture: OnEndManipulation(twoFingerDragGesture); break; } } static bool IsTouchPositionBlockedByUI(int fingerId) { return EventSystem.current != null && EventSystem.current.IsPointerOverGameObject(fingerId); } static bool IsGestureBlockedByUI(Gesture gesture) where T : Gesture { switch (gesture) { case DragGesture dragGesture: return IsTouchPositionBlockedByUI(dragGesture.fingerId); case PinchGesture pinchGesture: return IsTouchPositionBlockedByUI(pinchGesture.fingerId1) || IsTouchPositionBlockedByUI(pinchGesture.fingerId2); case TapGesture tapGesture: return IsTouchPositionBlockedByUI(tapGesture.fingerId); case TwistGesture twistGesture: return IsTouchPositionBlockedByUI(twistGesture.fingerId1) || IsTouchPositionBlockedByUI(twistGesture.fingerId2); case TwoFingerDragGesture twoFingerDragGesture: return IsTouchPositionBlockedByUI(twoFingerDragGesture.fingerId1) || IsTouchPositionBlockedByUI(twoFingerDragGesture.fingerId2); } return false; } /// /// /// IsHoverableBy(XRBaseInteractor) has been deprecated. Use instead. /// [Obsolete("IsHoverableBy(XRBaseInteractor) has been deprecated. Use IsHoverableBy(IXRHoverInteractor) instead.", true)] public override bool IsHoverableBy(XRBaseInteractor interactor) => default; /// /// /// IsSelectableBy(XRBaseInteractor) has been deprecated. Use instead. /// [Obsolete("IsSelectableBy(XRBaseInteractor) has been deprecated. Use IsSelectableBy(IXRSelectInteractor) instead.", true)] public override bool IsSelectableBy(XRBaseInteractor interactor) => default; } } #endif