// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; using System.Collections.Generic; using Unity.Profiling; namespace Microsoft.MixedReality.Toolkit.Input { /// /// The default implementation for pointer mediation in MRTK which is responsible for /// determining which pointers are active based on the state of all pointers. /// For example, one of the key things this class does is disable far pointers when a near pointer is close to an object. /// public class DefaultPointerMediator : IMixedRealityPointerMediator { protected readonly HashSet allPointers = new HashSet(); protected readonly HashSet farInteractPointers = new HashSet(); protected readonly HashSet nearInteractPointers = new HashSet(); protected readonly HashSet teleportPointers = new HashSet(); protected readonly HashSet unassignedPointers = new HashSet(); protected readonly Dictionary> pointerByInputSourceParent = new Dictionary>(); protected IPointerPreferences pointerPreferences; public DefaultPointerMediator() { pointerPreferences = null; } [Obsolete("Use DefaultPointerMediator() instead, followed by a call to SetPointerPreferences()")] public DefaultPointerMediator(IPointerPreferences pointerPrefs) { pointerPreferences = pointerPrefs; } private static readonly ProfilerMarker RegisterPointersPerfMarker = new ProfilerMarker("[MRTK] DefaultPointerMediator.RegisterPointers"); public virtual void RegisterPointers(IMixedRealityPointer[] pointers) { using (RegisterPointersPerfMarker.Auto()) { for (int i = 0; i < pointers.Length; i++) { IMixedRealityPointer pointer = pointers[i]; allPointers.Add(pointer); pointer.IsActive = true; if (pointer is IMixedRealityTeleportPointer) { teleportPointers.Add(pointer as IMixedRealityTeleportPointer); } else if (pointer is IMixedRealityNearPointer) { nearInteractPointers.Add(pointer as IMixedRealityNearPointer); } else { farInteractPointers.Add(pointer); } if (pointer.InputSourceParent != null) { HashSet children; if (!pointerByInputSourceParent.TryGetValue(pointer.InputSourceParent, out children)) { children = new HashSet(); pointerByInputSourceParent.Add(pointer.InputSourceParent, children); } children.Add(pointer); } } } } private static readonly ProfilerMarker UnregisterPointersPerfMarker = new ProfilerMarker("[MRTK] DefaultPointerMediator.UnregisterPointers"); public virtual void UnregisterPointers(IMixedRealityPointer[] pointers) { using (UnregisterPointersPerfMarker.Auto()) { for (int i = 0; i < pointers.Length; i++) { IMixedRealityPointer pointer = pointers[i]; allPointers.Remove(pointer); farInteractPointers.Remove(pointer); nearInteractPointers.Remove(pointer as IMixedRealityNearPointer); teleportPointers.Remove(pointer as IMixedRealityTeleportPointer); foreach (HashSet siblingPointers in pointerByInputSourceParent.Values) { siblingPointers.Remove(pointer); } } } } private static readonly ProfilerMarker UpdatePointersPerfMarker = new ProfilerMarker("[MRTK] DefaultPointerMediator.UpdatePointers"); public virtual void UpdatePointers() { using (UpdatePointersPerfMarker.Auto()) { // If there's any teleportation going on, disable all pointers except the teleporter foreach (IMixedRealityTeleportPointer pointer in teleportPointers) { if (pointer.TeleportRequestRaised) { pointer.IsActive = true; foreach (IMixedRealityPointer otherPointer in allPointers) { if (otherPointer.PointerId == pointer.PointerId) { continue; } otherPointer.IsActive = false; } // Don't do any further checks return; } } // pointers whose active state has not yet been set this frame unassignedPointers.Clear(); foreach (IMixedRealityPointer unassignedPointer in allPointers) { unassignedPointers.Add(unassignedPointer); } ApplyCustomPointerBehaviors(); // If any pointers are locked, they have priority. // Deactivate all other pointers that are on that input source foreach (IMixedRealityPointer pointer in allPointers) { if (pointer.IsFocusLocked) { pointer.IsActive = true; unassignedPointers.Remove(pointer); HashSet children; if (pointer.InputSourceParent != null && pointerByInputSourceParent.TryGetValue(pointer.InputSourceParent, out children)) { foreach (IMixedRealityPointer otherPointer in children) { if (!unassignedPointers.Contains(otherPointer)) { continue; } otherPointer.IsActive = false; unassignedPointers.Remove(otherPointer); } } } } // Check for near and far interactions // Any far interact pointers become disabled when a near pointer is near an object foreach (IMixedRealityNearPointer pointer in nearInteractPointers) { if (!unassignedPointers.Contains(pointer)) { continue; } if (pointer.IsNearObject) { pointer.IsActive = true; unassignedPointers.Remove(pointer); HashSet children; if (pointer.InputSourceParent != null && pointerByInputSourceParent.TryGetValue(pointer.InputSourceParent, out children)) { foreach (IMixedRealityPointer otherPointer in children) { if (!unassignedPointers.Contains(otherPointer)) { continue; } if (otherPointer is IMixedRealityNearPointer) { // Only disable far interaction pointers // It is okay for example to have two near pointers active on a single controller // like a poke pointer and a grab pointer continue; } otherPointer.IsActive = false; unassignedPointers.Remove(otherPointer); } } } } // Check for far interactions // Any far pointer other than GGV has priority over GGV foreach (IMixedRealityPointer pointer in farInteractPointers) { if (!unassignedPointers.Contains(pointer)) { continue; } if (!(pointer is GGVPointer)) { pointer.IsActive = true; unassignedPointers.Remove(pointer); HashSet children; if (pointer.InputSourceParent != null && pointerByInputSourceParent.TryGetValue(pointer.InputSourceParent, out children)) { foreach (IMixedRealityPointer otherPointer in children) { if (!unassignedPointers.Contains(otherPointer) || !farInteractPointers.Contains(otherPointer)) { continue; } if (otherPointer is GGVPointer) { // Disable the GGV pointer of an input source // when there is another far pointer belonging to the same source otherPointer.IsActive = false; unassignedPointers.Remove(otherPointer); } } } } } // All other pointers that have not been assigned this frame // have no reason to be disabled, so make sure they are active foreach (IMixedRealityPointer unassignedPointer in unassignedPointers) { unassignedPointer.IsActive = true; } } } /// public void SetPointerPreferences(IPointerPreferences pointerPreferences) { this.pointerPreferences = pointerPreferences; } private static readonly ProfilerMarker ApplyCustomPointerBehaviorsPerfMarker = new ProfilerMarker("[MRTK] DefaultPointerMediator.ApplyCustomPointerBehaviors"); private void ApplyCustomPointerBehaviors() { using (ApplyCustomPointerBehaviorsPerfMarker.Auto()) { if (pointerPreferences != null) { foreach (IMixedRealityPointer pointer in allPointers) { ApplyPointerBehavior(pointer, pointerPreferences.GetPointerBehavior(pointer)); } } } } private static readonly ProfilerMarker ApplyPointerBehaviorPerfMarker = new ProfilerMarker("[MRTK] DefaultPointerMediator.ApplyPointerBehavior"); private void ApplyPointerBehavior(IMixedRealityPointer pointer, PointerBehavior behavior) { using (ApplyPointerBehaviorPerfMarker.Auto()) { if (behavior == PointerBehavior.Default) { return; } bool isPointerOn = behavior == PointerBehavior.AlwaysOn; pointer.IsActive = isPointerOn; if (pointer is GenericPointer genericPtr) { genericPtr.IsInteractionEnabled = isPointerOn; } unassignedPointers.Remove(pointer); } } } }