// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.MixedReality.Toolkit.Utilities; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; #if UNITY_EDITOR using UnityEditor; #endif // UNITY_EDITOR using UnityEngine; using UnityEngine.Serialization; namespace Microsoft.MixedReality.Toolkit.Input { /// /// New controller types can be registered by adding the MixedRealityControllerAttribute to /// the controller class. /// [CreateAssetMenu(menuName = "Mixed Reality/Toolkit/Profiles/Mixed Reality Controller Mapping Profile", fileName = "MixedRealityControllerMappingProfile", order = (int)CreateProfileMenuItemIndices.ControllerMapping)] public class MixedRealityControllerMappingProfile : BaseMixedRealityProfile { [SerializeField] [Tooltip("The list of controller mappings your application can use.")] [FormerlySerializedAs("mixedRealityControllerMappingProfiles")] private MixedRealityControllerMapping[] mixedRealityControllerMappings = Array.Empty(); /// /// The list of controller mappings your application can use. /// public MixedRealityControllerMapping[] MixedRealityControllerMappings => mixedRealityControllerMappings; [Obsolete("MixedRealityControllerMappingProfiles is obsolete. Please use MixedRealityControllerMappings.")] public MixedRealityControllerMapping[] MixedRealityControllerMappingProfiles => mixedRealityControllerMappings; #if UNITY_EDITOR [MenuItem("Mixed Reality/Toolkit/Utilities/Update/Controller Mapping Profiles")] private static void UpdateAllControllerMappingProfiles() { string[] guids = AssetDatabase.FindAssets("t:MixedRealityControllerMappingProfile"); string[] assetPaths = new string[guids.Length]; for (int i = 0; i < guids.Length; i++) { string guid = guids[i]; assetPaths[i] = AssetDatabase.GUIDToAssetPath(guid); MixedRealityControllerMappingProfile asset = AssetDatabase.LoadAssetAtPath(assetPaths[i], typeof(MixedRealityControllerMappingProfile)) as MixedRealityControllerMappingProfile; List updatedMappings = new List(); foreach (MixedRealityControllerMapping mapping in asset.MixedRealityControllerMappings) { if (mapping.ControllerType.Type == null) { continue; } if (!mapping.HasCustomInteractionMappings) { mapping.UpdateInteractionSettingsFromDefault(); } updatedMappings.Add(mapping); } asset.mixedRealityControllerMappings = updatedMappings.ToArray(); } AssetDatabase.ForceReserializeAssets(assetPaths); } private static Type[] controllerMappingTypes; public static Type[] ControllerMappingTypes { get { CollectControllerTypes(); return controllerMappingTypes; } } public static Type[] CustomControllerMappingTypes { get => (from type in ControllerMappingTypes where UsesCustomInteractionMapping(type) select type).ToArray(); } private static void CollectControllerTypes() { if (controllerMappingTypes == null) { List controllerTypes = new List(); foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { IEnumerable types = null; try { types = assembly.ExportedTypes; } catch (NotSupportedException) { // assembly.ExportedTypes may not be supported. } catch (ReflectionTypeLoadException e) { // Not all assemblies may load correctly, but even upon encountering error // some subset may have loaded in. if (e.Types != null) { List loadedTypes = new List(); foreach (Type type in e.Types) { // According to API docs, this array may contain null values // so they must be filtered out here. if (type != null) { loadedTypes.Add(type); } } types = loadedTypes; } } if (types != null) { foreach (Type type in types) { if (type.IsSubclassOf(typeof(BaseController)) && MixedRealityControllerAttribute.Find(type) != null) { controllerTypes.Add(type); } } } } controllerMappingTypes = controllerTypes.ToArray(); } } public void Awake() { AddMappings(); SortMappings(); } private void AddMappings() { foreach (var controllerType in ControllerMappingTypes) { // Don't auto-add custom mappings when migrating, these can be removed by the user in the inspector. if (UsesCustomInteractionMapping(controllerType)) { continue; } foreach (Handedness handedness in GetSupportedHandedness(controllerType)) { // Try to find index of mapping in asset. int idx = Array.FindIndex(MixedRealityControllerMappings, 0, MixedRealityControllerMappings.Length, profile => profile.ControllerType.Type == controllerType && profile.Handedness == handedness); if (idx < 0) { var newMapping = new MixedRealityControllerMapping(controllerType, handedness); newMapping.SetDefaultInteractionMapping(overwrite: false); // Re-use existing mapping with the same supported controller type. foreach (var otherMapping in mixedRealityControllerMappings) { if (otherMapping.SupportedControllerType == newMapping.SupportedControllerType && otherMapping.Handedness == newMapping.Handedness) { try { newMapping.SynchronizeInputActions(otherMapping.Interactions); } catch (ArgumentException e) { Debug.LogError($"Controller mappings between {newMapping.Description} and {otherMapping.Description} do not match. Error message: {e.Message}"); } break; } } idx = mixedRealityControllerMappings.Length; Array.Resize(ref mixedRealityControllerMappings, idx + 1); mixedRealityControllerMappings[idx] = newMapping; } else { mixedRealityControllerMappings[idx].UpdateInteractionSettingsFromDefault(); } } } } private void SortMappings() { Array.Sort(mixedRealityControllerMappings, (profile1, profile2) => { bool isOptional1 = (profile1.ControllerType.Type == null || profile1.HasCustomInteractionMappings); bool isOptional2 = (profile2.ControllerType.Type == null || profile2.HasCustomInteractionMappings); if (!isOptional1 && !isOptional2) { int idx1 = Array.FindIndex(ControllerMappingTypes, type => type == profile1.ControllerType.Type); int idx2 = Array.FindIndex(ControllerMappingTypes, type => type == profile2.ControllerType.Type); if (idx1 == idx2) { idx1 = (int)profile1.Handedness; idx2 = (int)profile2.Handedness; } return Math.Sign(idx1 - idx2); } if (isOptional1 && isOptional2) { return 0; } return isOptional1 ? 1 : -1; // Put custom mappings at the end. These can be added / removed in the inspector. }); } private static bool UsesCustomInteractionMapping(Type controllerType) { var attribute = MixedRealityControllerAttribute.Find(controllerType); return attribute != null && attribute.Flags.HasFlag(MixedRealityControllerConfigurationFlags.UseCustomInteractionMappings); } private static Handedness[] GetSupportedHandedness(Type controllerType) { var attribute = MixedRealityControllerAttribute.Find(controllerType); return attribute != null ? attribute.SupportedHandedness : Array.Empty(); } #endif // UNITY_EDITOR } }