294 lines
12 KiB
C#
294 lines
12 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using Unity.Profiling;
|
|
|
|
namespace Microsoft.MixedReality.Toolkit.Input
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public class DefaultPointerMediator : IMixedRealityPointerMediator
|
|
{
|
|
protected readonly HashSet<IMixedRealityPointer> allPointers = new HashSet<IMixedRealityPointer>();
|
|
protected readonly HashSet<IMixedRealityPointer> farInteractPointers = new HashSet<IMixedRealityPointer>();
|
|
protected readonly HashSet<IMixedRealityNearPointer> nearInteractPointers = new HashSet<IMixedRealityNearPointer>();
|
|
protected readonly HashSet<IMixedRealityTeleportPointer> teleportPointers = new HashSet<IMixedRealityTeleportPointer>();
|
|
protected readonly HashSet<IMixedRealityPointer> unassignedPointers = new HashSet<IMixedRealityPointer>();
|
|
protected readonly Dictionary<IMixedRealityInputSource, HashSet<IMixedRealityPointer>> pointerByInputSourceParent = new Dictionary<IMixedRealityInputSource, HashSet<IMixedRealityPointer>>();
|
|
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<IMixedRealityPointer> children;
|
|
if (!pointerByInputSourceParent.TryGetValue(pointer.InputSourceParent, out children))
|
|
{
|
|
children = new HashSet<IMixedRealityPointer>();
|
|
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<IMixedRealityPointer> 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<IMixedRealityPointer> 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<IMixedRealityPointer> 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<IMixedRealityPointer> 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|