// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.MixedReality.Toolkit.Utilities; using Unity.Profiling; using UnityEngine; using UInput = UnityEngine.Input; namespace Microsoft.MixedReality.Toolkit.Input.UnityInput { [MixedRealityController( SupportedControllerType.GenericUnity, new[] { Handedness.None }, flags: MixedRealityControllerConfigurationFlags.UseCustomInteractionMappings)] public class GenericJoystickController : BaseController { public GenericJoystickController( TrackingState trackingState, Handedness controllerHandedness, IMixedRealityInputSource inputSource = null, MixedRealityInteractionMapping[] interactions = null) : this(trackingState, controllerHandedness, null, inputSource, interactions) { } public GenericJoystickController( TrackingState trackingState, Handedness controllerHandedness, IMixedRealityInputSourceDefinition definition, IMixedRealityInputSource inputSource = null, MixedRealityInteractionMapping[] interactions = null) : base(trackingState, controllerHandedness, inputSource, interactions, definition) { // Update the spatial pointer rotation with the preconfigured offset angle if (PointerOffsetAngle != 0f && Interactions != null) { MixedRealityInteractionMapping pointerMapping = null; for (int i = 0; i < Interactions.Length; i++) { MixedRealityInteractionMapping mapping = Interactions[i]; if (mapping.InputType == DeviceInputType.SpatialPointer) { pointerMapping = mapping; break; } } if (pointerMapping == null) { Debug.LogWarning($"A pointer offset is defined for {GetType()}, but no spatial pointer mapping could be found."); return; } MixedRealityPose startingRotation = MixedRealityPose.ZeroIdentity; startingRotation.Rotation *= Quaternion.AngleAxis(PointerOffsetAngle, Vector3.left); pointerMapping.PoseData = startingRotation; } } /// /// The pointer's offset angle. /// public virtual float PointerOffsetAngle { get; protected set; } = 0f; private Vector2 dualAxisPosition = Vector2.zero; private MixedRealityPose pointerOffsetPose = MixedRealityPose.ZeroIdentity; /// /// The current position of this controller. /// protected Vector3 CurrentControllerPosition = Vector3.zero; /// /// The current rotation of this controller. /// protected Quaternion CurrentControllerRotation = Quaternion.identity; /// /// The previous pose of this controller. /// protected MixedRealityPose LastControllerPose = MixedRealityPose.ZeroIdentity; /// /// The current pose of this controller. /// protected MixedRealityPose CurrentControllerPose = MixedRealityPose.ZeroIdentity; /// public override MixedRealityInteractionMapping[] DefaultInteractions => BuildInteractions(Definition?.GetDefaultMappings(ControllerHandedness), LegacyInputSupport); protected virtual MixedRealityInteractionMappingLegacyInput[] LegacyInputSupport { get; } = null; /// public override MixedRealityInteractionMapping[] DefaultLeftHandedInteractions => BuildInteractions(Definition?.GetDefaultMappings(Handedness.Left), LeftHandedLegacyInputSupport); protected virtual MixedRealityInteractionMappingLegacyInput[] LeftHandedLegacyInputSupport { get; } = null; /// public override MixedRealityInteractionMapping[] DefaultRightHandedInteractions => BuildInteractions(Definition?.GetDefaultMappings(Handedness.Right), RightHandedLegacyInputSupport); protected virtual MixedRealityInteractionMappingLegacyInput[] RightHandedLegacyInputSupport { get; } = null; private MixedRealityInteractionMapping[] BuildInteractions(System.Collections.Generic.IReadOnlyList definitionInteractions, MixedRealityInteractionMappingLegacyInput[] legacyInputs) { if (definitionInteractions == null) { return null; } // If the legacy array is null, it may not have been overridden and thus isn't needed. Move on and build the array without it. if (legacyInputs != null && definitionInteractions.Count != legacyInputs.Length) { Debug.LogWarning($"Legacy input mappings are being used, but an incorrect number of mappings were provided. Interaction count {definitionInteractions.Count}. Legacy count {legacyInputs.Length}."); return null; } MixedRealityInteractionMapping[] defaultInteractions = new MixedRealityInteractionMapping[definitionInteractions.Count]; for (int i = 0; i < definitionInteractions.Count; i++) { if (legacyInputs != null) { defaultInteractions[i] = new MixedRealityInteractionMapping((uint)i, definitionInteractions[i], legacyInputs[i]); } else { defaultInteractions[i] = new MixedRealityInteractionMapping((uint)i, definitionInteractions[i]); } } return defaultInteractions; } private static readonly ProfilerMarker UpdateControllerPerfMarker = new ProfilerMarker("[MRTK] GenericJoystickController.UpdateController"); /// /// Update the controller data from Unity's Input Manager /// public virtual void UpdateController() { using (UpdateControllerPerfMarker.Auto()) { if (!Enabled) { return; } if (Interactions == null) { Debug.LogError($"No interaction configuration for {GetType().Name}"); Enabled = false; } for (int i = 0; i < Interactions?.Length; i++) { switch (Interactions[i].AxisType) { case AxisType.None: break; case AxisType.Digital: UpdateButtonData(Interactions[i]); break; case AxisType.SingleAxis: UpdateSingleAxisData(Interactions[i]); break; case AxisType.DualAxis: UpdateDualAxisData(Interactions[i]); break; case AxisType.SixDof: UpdatePoseData(Interactions[i]); break; default: Debug.LogError($"Input [{Interactions[i].InputType}] is not handled for this controller [{GetType().Name}]"); break; } } } } private static readonly ProfilerMarker UpdateButtonDataPerfMarker = new ProfilerMarker("[MRTK] GenericJoystickController.UpdateButtonData"); /// /// Update an Interaction Bool data type from a Bool input /// /// /// Raises an Input System "Input Down" event when the key is down, and raises an "Input Up" when it is released (e.g. a Button). /// Also raises a "Pressed" event while pressed. /// protected void UpdateButtonData(MixedRealityInteractionMapping interactionMapping) { using (UpdateButtonDataPerfMarker.Auto()) { Debug.Assert(interactionMapping.AxisType == AxisType.Digital); // Update the interaction data source switch (interactionMapping.InputType) { case DeviceInputType.TriggerPress: interactionMapping.BoolData = UInput.GetAxisRaw(interactionMapping.AxisCodeX).Equals(1); break; case DeviceInputType.TriggerTouch: case DeviceInputType.TriggerNearTouch: case DeviceInputType.ThumbNearTouch: case DeviceInputType.IndexFingerNearTouch: case DeviceInputType.MiddleFingerNearTouch: case DeviceInputType.RingFingerNearTouch: case DeviceInputType.PinkyFingerNearTouch: interactionMapping.BoolData = interactionMapping.KeyCode == KeyCode.None ? !UInput.GetAxisRaw(interactionMapping.AxisCodeX).Equals(0) : UInput.GetKey(interactionMapping.KeyCode); break; default: interactionMapping.BoolData = UInput.GetKey(interactionMapping.KeyCode); break; } // If our value changed raise it. if (interactionMapping.Changed) { // Raise input system event if it's enabled if (interactionMapping.BoolData) { CoreServices.InputSystem?.RaiseOnInputDown(InputSource, ControllerHandedness, interactionMapping.MixedRealityInputAction); } else { CoreServices.InputSystem?.RaiseOnInputUp(InputSource, ControllerHandedness, interactionMapping.MixedRealityInputAction); } } } } private static readonly ProfilerMarker UpdateSingleAxisDataPerfMarker = new ProfilerMarker("[MRTK] GenericJoystickController.UpdateSingleAxisData"); /// /// Update an Interaction Float data type from a SingleAxis (float) input /// /// /// Raises a Float Input Changed event when the float data changes /// protected void UpdateSingleAxisData(MixedRealityInteractionMapping interactionMapping) { using (UpdateSingleAxisDataPerfMarker.Auto()) { Debug.Assert(interactionMapping.AxisType == AxisType.SingleAxis); var singleAxisValue = UInput.GetAxisRaw(interactionMapping.AxisCodeX); if (interactionMapping.InputType == DeviceInputType.TriggerPress || interactionMapping.InputType == DeviceInputType.GripPress) { interactionMapping.BoolData = singleAxisValue.Equals(1); // If our value changed raise it. if (interactionMapping.Changed) { // Raise input system event if it's enabled if (interactionMapping.BoolData) { CoreServices.InputSystem?.RaiseOnInputDown(InputSource, ControllerHandedness, interactionMapping.MixedRealityInputAction); } else { CoreServices.InputSystem?.RaiseOnInputUp(InputSource, ControllerHandedness, interactionMapping.MixedRealityInputAction); } } } else { // Update the interaction data source interactionMapping.FloatData = singleAxisValue; // If our value changed raise it. if (interactionMapping.Changed) { // Raise input system event if it's enabled CoreServices.InputSystem?.RaiseFloatInputChanged(InputSource, ControllerHandedness, interactionMapping.MixedRealityInputAction, interactionMapping.FloatData); } } } } private static readonly ProfilerMarker UpdateDualAxisDataPerfMarker = new ProfilerMarker("[MRTK] GenericJoystickController.UpdateDualAxisData"); /// /// Update the Touchpad / Thumbstick input from the device (in OpenVR, touchpad and thumbstick are the same input control) /// protected void UpdateDualAxisData(MixedRealityInteractionMapping interactionMapping) { using (UpdateDualAxisDataPerfMarker.Auto()) { Debug.Assert(interactionMapping.AxisType == AxisType.DualAxis); dualAxisPosition.x = UInput.GetAxisRaw(interactionMapping.AxisCodeX); dualAxisPosition.y = UInput.GetAxisRaw(interactionMapping.AxisCodeY); // Update the interaction data source interactionMapping.Vector2Data = dualAxisPosition; // If our value changed raise it. if (interactionMapping.Changed) { // Raise input system event if it's enabled CoreServices.InputSystem?.RaisePositionInputChanged(InputSource, ControllerHandedness, interactionMapping.MixedRealityInputAction, interactionMapping.Vector2Data); } } } private static readonly ProfilerMarker UpdatePoseDataPerfMarker = new ProfilerMarker("[MRTK] GenericJoystickController.UpdatePoseData"); /// /// Update Spatial Pointer Data. /// protected void UpdatePoseData(MixedRealityInteractionMapping interactionMapping) { using (UpdatePoseDataPerfMarker.Auto()) { Debug.Assert(interactionMapping.AxisType == AxisType.SixDof); if (interactionMapping.InputType == DeviceInputType.SpatialPointer) { pointerOffsetPose.Position = CurrentControllerPose.Position; pointerOffsetPose.Rotation = CurrentControllerPose.Rotation * Quaternion.AngleAxis(PointerOffsetAngle, Vector3.left); // Update the interaction data source interactionMapping.PoseData = pointerOffsetPose; } else if (interactionMapping.InputType == DeviceInputType.SpatialGrip) { // Update the interaction data source interactionMapping.PoseData = CurrentControllerPose; } else { Debug.LogWarning("Unhandled Interaction"); return; } // If our value changed raise it. if (interactionMapping.Changed) { // Raise input system event if it's enabled CoreServices.InputSystem?.RaisePoseInputChanged(InputSource, ControllerHandedness, interactionMapping.MixedRealityInputAction, interactionMapping.PoseData); } } } } }