// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Utilities;
using System;
using System.Runtime.CompilerServices;
using UnityEngine;
[assembly: InternalsVisibleTo("Microsoft.MixedReality.Toolkit.Editor.Inspectors")]
namespace Microsoft.MixedReality.Toolkit.Input
{
///
/// Used to define a controller or other input device's physical buttons, and other attributes.
///
[Serializable]
public struct MixedRealityControllerMapping
{
///
/// Constructor.
///
/// Controller Type to instantiate at runtime.
/// The designated hand that the device is managing.
public MixedRealityControllerMapping(Type controllerType, Handedness handedness = Handedness.None) : this()
{
this.controllerType = new SystemType(controllerType);
this.handedness = handedness;
interactions = null;
}
///
/// Description of the Device.
///
public string Description
{
get
{
string controllerName = "Unknown";
if (controllerType.Type != null)
{
var attr = MixedRealityControllerAttribute.Find(controllerType);
if (attr != null)
{
controllerName = attr.SupportedControllerType.ToString().ToProperCase();
}
}
string handednessText = string.Empty;
switch (handedness)
{
case Handedness.Left:
case Handedness.Right:
handednessText = $"{handedness} Hand ";
// Avoid multiple occurrences of "Hand":
controllerName = controllerName.Replace("Hand", "").Trim();
break;
}
return $"{controllerName} {handednessText}Controller";
}
}
[SerializeField]
[Tooltip("Controller type to instantiate at runtime.")]
[Implements(typeof(IMixedRealityController), TypeGrouping.ByNamespaceFlat)]
private SystemType controllerType;
///
/// Controller Type to instantiate at runtime.
///
public SystemType ControllerType => controllerType;
public SupportedControllerType SupportedControllerType
{
get
{
if (controllerType.Type != null)
{
var attr = MixedRealityControllerAttribute.Find(controllerType);
if (attr != null)
{
return attr.SupportedControllerType;
}
}
return 0;
}
}
[SerializeField]
[Tooltip("The designated hand that the device is managing.")]
private Handedness handedness;
///
/// The designated hand that the device is managing.
///
public Handedness Handedness => handedness;
///
/// Is this controller mapping using custom interactions?
///
public bool HasCustomInteractionMappings
{
get
{
if (controllerType.Type != null)
{
var attr = MixedRealityControllerAttribute.Find(controllerType);
if (attr != null)
{
return attr.Flags.HasFlag(MixedRealityControllerConfigurationFlags.UseCustomInteractionMappings);
}
}
return false;
}
}
[SerializeField]
[Tooltip("Details the list of available buttons / interactions available from the device.")]
private MixedRealityInteractionMapping[] interactions;
///
/// Details the list of available buttons / interactions available from the device.
///
public MixedRealityInteractionMapping[] Interactions => interactions;
///
/// Sets the default interaction mapping based on the current controller type.
///
internal void SetDefaultInteractionMapping(bool overwrite = false)
{
if (interactions == null || interactions.Length == 0 || overwrite)
{
MixedRealityInteractionMapping[] defaultMappings = GetDefaultInteractionMappings();
if (defaultMappings != null)
{
interactions = defaultMappings;
}
}
}
internal bool UpdateInteractionSettingsFromDefault()
{
if (interactions == null || interactions.Length == 0) { return false; }
MixedRealityInteractionMapping[] newDefaultInteractions = GetDefaultInteractionMappings();
if (newDefaultInteractions == null)
{
return false;
}
if (interactions.Length != newDefaultInteractions.Length)
{
interactions = CreateNewMatchedMapping(interactions, newDefaultInteractions);
return true;
}
bool updatedMappings = false;
for (int i = 0; i < newDefaultInteractions.Length; i++)
{
MixedRealityInteractionMapping currentMapping = interactions[i];
MixedRealityInteractionMapping currentDefaultMapping = newDefaultInteractions[i];
if (!Equals(currentMapping, currentDefaultMapping))
{
interactions[i] = new MixedRealityInteractionMapping(currentDefaultMapping)
{
MixedRealityInputAction = currentMapping.MixedRealityInputAction
};
updatedMappings = true;
}
}
return updatedMappings;
}
private MixedRealityInteractionMapping[] CreateNewMatchedMapping(MixedRealityInteractionMapping[] interactions, MixedRealityInteractionMapping[] newDefaultInteractions)
{
MixedRealityInteractionMapping[] newDefaultMapping = new MixedRealityInteractionMapping[newDefaultInteractions.Length];
for (int i = 0; i < newDefaultInteractions.Length; i++)
{
for (int j = 0; j < interactions.Length; j++)
{
if (Equals(interactions[j], newDefaultInteractions[i]))
{
newDefaultMapping[i] = new MixedRealityInteractionMapping(newDefaultInteractions[i])
{
MixedRealityInputAction = interactions[j].MixedRealityInputAction
};
break;
}
}
if (newDefaultMapping[i] == null)
{
newDefaultMapping[i] = new MixedRealityInteractionMapping(newDefaultInteractions[i]);
}
}
return newDefaultMapping;
}
private bool Equals(MixedRealityInteractionMapping a, MixedRealityInteractionMapping b)
{
return !(a.Description != b.Description ||
a.AxisType != b.AxisType ||
a.InputType != b.InputType ||
a.KeyCode != b.KeyCode ||
a.AxisCodeX != b.AxisCodeX ||
a.AxisCodeY != b.AxisCodeY ||
a.InvertXAxis != b.InvertXAxis ||
a.InvertYAxis != b.InvertYAxis);
}
private MixedRealityInteractionMapping[] GetDefaultInteractionMappings()
{
if (Activator.CreateInstance(controllerType, TrackingState.NotTracked, handedness, null, null) is BaseController detectedController)
{
switch (handedness)
{
case Handedness.Left:
return detectedController.DefaultLeftHandedInteractions ?? detectedController.DefaultInteractions;
case Handedness.Right:
return detectedController.DefaultRightHandedInteractions ?? detectedController.DefaultInteractions;
default:
return detectedController.DefaultInteractions;
}
}
return null;
}
///
/// Synchronizes the input actions of the same physical controller of a different concrete type.
///
internal void SynchronizeInputActions(MixedRealityInteractionMapping[] otherControllerMapping)
{
if (otherControllerMapping.Length != interactions.Length)
{
throw new ArgumentException($"otherControllerMapping length {otherControllerMapping.Length} does not match this length {interactions.Length}.");
}
for (int i = 0; i < interactions.Length; i++)
{
interactions[i].MixedRealityInputAction = otherControllerMapping[i].MixedRealityInputAction;
}
}
}
}