// 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);
}
}
}
}