mirror of
https://udrimavric.com/MAVRIC/Stratasys-450mc-VR.git
synced 2025-01-23 23:58:36 -05:00
415 lines
16 KiB
C#
415 lines
16 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine.InputSystem;
|
|
using UnityEngine.InputSystem.Controls;
|
|
|
|
namespace UnityEngine.XR.Content.Interaction
|
|
{
|
|
/// <summary>
|
|
/// Allows for actions to 'lock' the input controls they use so that others actions using the same controls will not receive input at the same time.
|
|
/// InputMediator works by injecting input processors on every binding in active action maps.
|
|
/// Usage is via <see cref="ConsumeControl"/> and <see cref="ReleaseControl"/>.
|
|
/// </summary>
|
|
public static class InputMediator
|
|
{
|
|
/// <summary>
|
|
/// Substring of the names that all of the input processors that are injected have.
|
|
/// </summary>
|
|
/// <seealso cref="Initialize"/>
|
|
const string k_ConsumeKey = "Consume";
|
|
static bool s_Updating;
|
|
|
|
// Data associated with each control, storing if an action has locked it,
|
|
// and other actions that are allowed to make use of this control at the same time
|
|
class ConsumptionState
|
|
{
|
|
public int m_LockedAction = -1;
|
|
public int m_AllowedAction1 = -1;
|
|
public int m_AllowedAction2 = -1;
|
|
|
|
public bool m_Automatic;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generic Consumption processor - handles all the aspects of looking up actions that have locked a control
|
|
/// Implementations merely need to implement the methods to determine if a control has returned to rest (and thus should reset)
|
|
/// And the 'identity' value of a control, which is the value it should have when the control is locked
|
|
/// </summary>
|
|
/// <typeparam name="TValue"></typeparam>
|
|
abstract class ConsumeProcessor<TValue> : InputProcessor<TValue> where TValue : struct
|
|
{
|
|
public int m_ActionIndex = -1;
|
|
|
|
public override TValue Process(TValue value, InputControl control)
|
|
{
|
|
// Check the dictionary for this control
|
|
// If it does not exist, proceed unhindered
|
|
if (control == null || !s_ConsumedControls.TryGetValue(control, out var currentState))
|
|
return value;
|
|
|
|
// If there is no locked action, also proceed unhindered
|
|
if (currentState.m_LockedAction == -1)
|
|
return value;
|
|
|
|
// Check for an action match
|
|
var actionMatched = (currentState.m_LockedAction == m_ActionIndex) || (currentState.m_AllowedAction1 == m_ActionIndex) || (currentState.m_AllowedAction2 == m_ActionIndex);
|
|
|
|
// Check if we should automatically release
|
|
if (actionMatched)
|
|
{
|
|
if (currentState.m_Automatic && ValueNearZero(value))
|
|
currentState.m_LockedAction = -1;
|
|
|
|
return value;
|
|
}
|
|
|
|
return IdentityValue();
|
|
}
|
|
|
|
public abstract bool ValueNearZero(TValue value);
|
|
|
|
public abstract TValue IdentityValue();
|
|
}
|
|
|
|
class ConsumeFloat : ConsumeProcessor<float>
|
|
{
|
|
public override bool ValueNearZero(float value)
|
|
{
|
|
return value < float.Epsilon;
|
|
}
|
|
|
|
public override float IdentityValue()
|
|
{
|
|
return 0.0f;
|
|
}
|
|
}
|
|
|
|
class ConsumeVector2 : ConsumeProcessor<Vector2>
|
|
{
|
|
public override bool ValueNearZero(Vector2 value)
|
|
{
|
|
return value.sqrMagnitude < float.Epsilon;
|
|
}
|
|
|
|
public override Vector2 IdentityValue()
|
|
{
|
|
return Vector2.zero;
|
|
}
|
|
}
|
|
|
|
class ConsumeVector3 : ConsumeProcessor<Vector3>
|
|
{
|
|
public override bool ValueNearZero(Vector3 value)
|
|
{
|
|
return value.sqrMagnitude < float.Epsilon;
|
|
}
|
|
|
|
public override Vector3 IdentityValue()
|
|
{
|
|
return Vector3.zero;
|
|
}
|
|
}
|
|
|
|
class ConsumeQuaternion : ConsumeProcessor<Quaternion>
|
|
{
|
|
public override bool ValueNearZero(Quaternion value)
|
|
{
|
|
return Quaternion.Angle(value, Quaternion.identity) < float.Epsilon;
|
|
}
|
|
|
|
public override Quaternion IdentityValue()
|
|
{
|
|
return Quaternion.identity;
|
|
}
|
|
}
|
|
|
|
static Dictionary<InputControl, ConsumptionState> s_ConsumedControls = new Dictionary<InputControl, ConsumptionState>();
|
|
static Dictionary<InputAction, int> s_ActionIndices = new Dictionary<InputAction, int>();
|
|
static HashSet<InputAction> s_InitializedActions = new HashSet<InputAction>();
|
|
|
|
[RuntimeInitializeOnLoadMethod]
|
|
static void Initialize()
|
|
{
|
|
InputSystem.InputSystem.RegisterProcessor<ConsumeFloat>(nameof(ConsumeFloat));
|
|
InputSystem.InputSystem.RegisterProcessor<ConsumeVector2>(nameof(ConsumeVector2));
|
|
InputSystem.InputSystem.RegisterProcessor<ConsumeVector3>(nameof(ConsumeVector3));
|
|
InputSystem.InputSystem.RegisterProcessor<ConsumeQuaternion>(nameof(ConsumeQuaternion));
|
|
Application.quitting += OnApplicationQuitting;
|
|
InputSystem.InputSystem.onActionChange += OnActionChange;
|
|
InitializeConsumeProcessors();
|
|
}
|
|
|
|
static void OnApplicationQuitting()
|
|
{
|
|
InputSystem.InputSystem.onActionChange -= OnActionChange;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to 'lock' the controls belonging to an action - which means other actions using the same control will only get zero/identity values during this time
|
|
/// </summary>
|
|
/// <param name="source">The action that should lock their controls</param>
|
|
/// <param name="automaticRelease">If the control lock should release automatically when the controls go to a resting state</param>
|
|
/// <param name="force">If the action should forcefully take a lock from another consuming action</param>
|
|
/// <param name="friendAction1">An additional action that can access these controls at this time</param>
|
|
/// <param name="friendAction2">An additional action that can access these controls at this time</param>
|
|
/// <returns>False if _any_ of the associated controls were unable to be locked </returns>
|
|
public static bool ConsumeControl(InputAction source, bool automaticRelease, bool force = false, InputAction friendAction1 = null, InputAction friendAction2 = null)
|
|
{
|
|
if (source == null)
|
|
return false;
|
|
|
|
var actionIndex1 = GetActionIndex(source);
|
|
var actionIndex2 = GetActionIndex(friendAction1);
|
|
var actionIndex3 = GetActionIndex(friendAction2);
|
|
var lockCount = 0;
|
|
|
|
var sourceControls = source.controls;
|
|
foreach (var currentControl in sourceControls)
|
|
{
|
|
// Check to see if it is in the list already
|
|
// If not, make an entry for it
|
|
if (!s_ConsumedControls.TryGetValue(currentControl, out var controlState))
|
|
{
|
|
var parent = currentControl.parent;
|
|
if (currentControl is AxisControl && parent is Vector2Control)
|
|
{
|
|
if (!s_ConsumedControls.TryGetValue(parent, out controlState))
|
|
{
|
|
controlState = new ConsumptionState { m_Automatic = automaticRelease };
|
|
s_ConsumedControls.Add(parent, controlState);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
controlState = new ConsumptionState { m_Automatic = automaticRelease };
|
|
}
|
|
|
|
s_ConsumedControls.Add(currentControl, controlState);
|
|
}
|
|
|
|
if (force || controlState.m_LockedAction == -1)
|
|
{
|
|
controlState.m_LockedAction = actionIndex1;
|
|
controlState.m_AllowedAction1 = actionIndex2;
|
|
controlState.m_AllowedAction2 = actionIndex3;
|
|
lockCount++;
|
|
}
|
|
}
|
|
return (lockCount == sourceControls.Count);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases an action's lock over its associated controls. Other actions using the same controls will begin receiving input again
|
|
/// </summary>
|
|
/// <param name="source">The action that is attempting to release its lock</param>
|
|
/// <param name="force">If this input lock should be released regardless of requesting action</param>
|
|
/// <returns>False if _any_ of the associated controls were unable to be released </returns>
|
|
public static bool ReleaseControl(InputAction source, bool force = false)
|
|
{
|
|
if (source == null)
|
|
return false;
|
|
|
|
var actionIndex = GetActionIndex(source);
|
|
var lockCount = 0;
|
|
|
|
var sourceControls = source.controls;
|
|
foreach (var currentControl in sourceControls)
|
|
{
|
|
// Check to see if it is in the list already
|
|
// If not, nothing to release
|
|
if (!s_ConsumedControls.TryGetValue(currentControl, out var controlState))
|
|
{
|
|
lockCount++;
|
|
continue;
|
|
}
|
|
|
|
if (force || controlState.m_LockedAction == actionIndex)
|
|
{
|
|
controlState.m_LockedAction = -1;
|
|
lockCount++;
|
|
}
|
|
}
|
|
|
|
return (lockCount == sourceControls.Count);
|
|
}
|
|
|
|
static void InitializeConsumeProcessors()
|
|
{
|
|
s_Updating = true;
|
|
var actionList = InputSystem.InputSystem.ListEnabledActions();
|
|
foreach (var action in actionList)
|
|
{
|
|
EnsureConsumeProcessorAdded(action);
|
|
|
|
// Since this list only contains currently enabled actions,
|
|
// any actions that are enabled later will need to
|
|
// have the consume processor added. Since those actions may not
|
|
// trigger a BoundControlsChanged change, the OnActionChange event handler
|
|
// will check against this list and append to it as actions are enabled.
|
|
// This set is checked against for performance reasons
|
|
// to avoid the more costly EnsureConsumeProcessorAdded(InputAction) method.
|
|
s_InitializedActions.Add(action);
|
|
}
|
|
s_Updating = false;
|
|
}
|
|
|
|
static void OnActionChange(object actionSource, InputActionChange change)
|
|
{
|
|
if (s_Updating)
|
|
return;
|
|
|
|
s_Updating = true;
|
|
|
|
if (change == InputActionChange.ActionEnabled)
|
|
{
|
|
var action = (InputAction)actionSource;
|
|
if (s_InitializedActions.Add(action))
|
|
EnsureConsumeProcessorAdded(action);
|
|
}
|
|
else if (change == InputActionChange.ActionMapEnabled)
|
|
{
|
|
var actionMap = (InputActionMap)actionSource;
|
|
foreach (var action in actionMap.actions)
|
|
{
|
|
if (s_InitializedActions.Add(action))
|
|
EnsureConsumeProcessorAdded(action);
|
|
}
|
|
}
|
|
else if (change == InputActionChange.BoundControlsChanged)
|
|
{
|
|
// We skip pure actions here as they can get into an invalid state if bindings were changed
|
|
if (actionSource is InputActionMap actionMap)
|
|
{
|
|
EnsureConsumeProcessorAdded(actionMap);
|
|
}
|
|
else if (actionSource is InputActionAsset actionAsset)
|
|
{
|
|
EnsureConsumeProcessorAdded(actionAsset);
|
|
}
|
|
}
|
|
|
|
s_Updating = false;
|
|
}
|
|
|
|
static string ControlTypeToConsumeType(string controlType)
|
|
{
|
|
switch (controlType)
|
|
{
|
|
case "Single":
|
|
case "Button":
|
|
case "float":
|
|
return nameof(ConsumeFloat);
|
|
case "Vector2":
|
|
return nameof(ConsumeVector2);
|
|
case "Vector3":
|
|
return nameof(ConsumeVector3);
|
|
case "Quaternion":
|
|
return nameof(ConsumeQuaternion);
|
|
}
|
|
return "";
|
|
}
|
|
|
|
static string ProcessBindingControl(string bindingPath)
|
|
{
|
|
var control = InputSystem.InputSystem.FindControl(bindingPath);
|
|
var consumeType = "";
|
|
|
|
if (control != null)
|
|
consumeType = ControlTypeToConsumeType(control.valueType.Name);
|
|
else
|
|
{
|
|
// Try to fall back based on path keywords
|
|
var bindingLower = bindingPath.ToLower();
|
|
if (bindingLower.EndsWith("position"))
|
|
consumeType = ControlTypeToConsumeType("Vector3");
|
|
|
|
if (bindingLower.EndsWith("rotation"))
|
|
consumeType = ControlTypeToConsumeType("Quaternion");
|
|
|
|
if (bindingLower.EndsWith("x"))
|
|
consumeType = ControlTypeToConsumeType("float");
|
|
|
|
if (bindingLower.EndsWith("y"))
|
|
consumeType = ControlTypeToConsumeType("float");
|
|
|
|
if (bindingLower.EndsWith("axis"))
|
|
consumeType = ControlTypeToConsumeType("Vector2");
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(consumeType))
|
|
return "";
|
|
|
|
return consumeType;
|
|
}
|
|
|
|
static void EnsureConsumeProcessorAdded(InputAction action)
|
|
{
|
|
var bindingCount = action.bindings.Count;
|
|
for (var i = 0; i < bindingCount; i++)
|
|
{
|
|
var currentBinding = action.bindings[i];
|
|
|
|
// Ignore composites, but not parts of composites
|
|
if (currentBinding.isComposite)
|
|
continue;
|
|
|
|
// Ignore bindings that aren't ready yet
|
|
if (currentBinding.effectiveProcessors == null)
|
|
continue;
|
|
|
|
var actionIndex = GetActionIndex(action);
|
|
if (!currentBinding.effectiveProcessors.Contains(k_ConsumeKey))
|
|
{
|
|
// Ignore unused bindings
|
|
if (string.IsNullOrEmpty(currentBinding.path))
|
|
continue;
|
|
|
|
// Get the binding's control type and cache it in the control lookup
|
|
var bindingType = ProcessBindingControl(currentBinding.path);
|
|
|
|
// If the composite can't figure out its type, then skip it
|
|
if (string.IsNullOrEmpty(bindingType))
|
|
{
|
|
//Debug.LogWarning($"Could not add consume processor for binding { currentBinding.path }, in {action.name}");
|
|
continue;
|
|
}
|
|
|
|
if (currentBinding.processors.Length > 0)
|
|
action.ApplyBindingOverride(i, new InputBinding { overrideProcessors = $"{bindingType}(m_ActionIndex={actionIndex}), {currentBinding.processors}" });
|
|
else
|
|
action.ApplyBindingOverride(i, new InputBinding { overrideProcessors = $"{bindingType}(m_ActionIndex={actionIndex})" });
|
|
}
|
|
}
|
|
}
|
|
|
|
static void EnsureConsumeProcessorAdded(InputActionMap actionMap)
|
|
{
|
|
foreach (var action in actionMap.actions)
|
|
{
|
|
EnsureConsumeProcessorAdded(action);
|
|
}
|
|
}
|
|
|
|
static void EnsureConsumeProcessorAdded(InputActionAsset actionAsset)
|
|
{
|
|
foreach (var map in actionAsset.actionMaps)
|
|
{
|
|
EnsureConsumeProcessorAdded(map);
|
|
}
|
|
}
|
|
|
|
static int GetActionIndex(InputAction source)
|
|
{
|
|
if (source == null)
|
|
return -1;
|
|
|
|
if (!s_ActionIndices.TryGetValue(source, out var actionIndex))
|
|
{
|
|
actionIndex = s_ActionIndices.Count;
|
|
s_ActionIndices.Add(source, actionIndex);
|
|
}
|
|
|
|
return actionIndex;
|
|
}
|
|
}
|
|
}
|