// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.MixedReality.Toolkit.Utilities; using UnityEngine; /// /// Provides per-frame data access to simulated controller data /// namespace Microsoft.MixedReality.Toolkit.Input { /// /// Internal class to define current state of a controller. /// internal abstract class SimulatedControllerState { protected Handedness handedness = Handedness.None; public Handedness Handedness => handedness; // Show a tracked controller public bool IsTracked = false; // Position of the controller in viewport space public Vector3 ViewportPosition = Vector3.zero; // Rotation of the controller relative to the camera public Vector3 ViewportRotation = Vector3.zero; // Random offset to simulate tracking inaccuracy public Vector3 JitterOffset = Vector3.zero; protected float viewportPositionZTarget; protected readonly float smoothScrollSpeed = 5f; public SimulatedControllerState(Handedness _handedness) { handedness = _handedness; } public void SimulateInput(MouseDelta mouseDelta, bool useMouseRotation, float rotationSensitivity, float rotationScale, float noiseAmount) { if (useMouseRotation) { Vector3 rotationDeltaEulerAngles = Vector3.zero; rotationDeltaEulerAngles.x += -mouseDelta.screenDelta.y * rotationSensitivity; rotationDeltaEulerAngles.y += mouseDelta.screenDelta.x * rotationSensitivity; rotationDeltaEulerAngles.z += mouseDelta.screenDelta.z * rotationSensitivity; rotationDeltaEulerAngles *= rotationScale; ViewportRotation += rotationDeltaEulerAngles; } else { ViewportPosition.x += mouseDelta.viewportDelta.x; ViewportPosition.y += mouseDelta.viewportDelta.y; viewportPositionZTarget += mouseDelta.viewportDelta.z; } JitterOffset = Random.insideUnitSphere * noiseAmount; } /// /// Resets simulated controller position. /// /// The position to reset controller to. public void ResetPosition(Vector3 resetTo) { ViewportPosition = resetTo; viewportPositionZTarget = ViewportPosition.z; } /// /// Resets simulated controller rotation. /// public abstract void ResetRotation(); /// /// Update information about the controller state or position. /// internal void Update() { ViewportPosition.z = Mathf.Lerp(ViewportPosition.z, viewportPositionZTarget, smoothScrollSpeed * Time.deltaTime); } } /// /// Produces simulated data every frame that defines the position and rotation of the simulated controller. /// public abstract class SimulatedControllerDataProvider { protected MixedRealityInputSimulationProfile profile; /// /// If true then the left controller is always visible, regardless of simulating. /// public bool IsAlwaysVisibleLeft = false; /// /// If true then the right controller is always visible, regardless of simulating. /// public bool IsAlwaysVisibleRight = false; internal SimulatedControllerState InputStateLeft; internal SimulatedControllerState InputStateRight; internal SimulatedControllerState InputStateGaze; private bool isSimulatingGaze => !IsSimulatingLeft && !IsSimulatingRight && !IsAlwaysVisibleLeft && !IsAlwaysVisibleRight && !DeviceUtility.IsPresent; /// /// Left controller is controlled by user input. /// public bool IsSimulatingLeft { get; private set; } = false; /// /// Right controller is controlled by user input. /// public bool IsSimulatingRight { get; private set; } = false; // Most recent time controller control was enabled, protected float lastSimulationLeft = -1.0e6f; protected float lastSimulationRight = -1.0e6f; protected float lastSimulationGaze = -1.0e6f; // Last timestamp when controllers were tracked protected long lastInputTrackedTimestampLeft = 0; protected long lastInputTrackedTimestampRight = 0; protected long lastInputTrackedTimestampGaze = 0; protected static readonly KeyBinding cancelRotationKey = KeyBinding.FromKey(KeyCode.Escape); protected readonly MouseRotationProvider mouseRotation = new MouseRotationProvider(); public SimulatedControllerDataProvider(MixedRealityInputSimulationProfile _profile) { profile = _profile; } /// /// Update controller state based on keyboard and mouse input /// protected virtual void SimulateUserInput(MouseDelta mouseDelta) { float time = Time.time; if (KeyInputSystem.GetKeyDown(profile.ToggleLeftControllerKey)) { IsAlwaysVisibleLeft = !IsAlwaysVisibleLeft; } if (KeyInputSystem.GetKeyDown(profile.ToggleRightControllerKey)) { IsAlwaysVisibleRight = !IsAlwaysVisibleRight; } if (!Application.isFocused && !KeyInputSystem.SimulatingInput) { IsSimulatingLeft = false; IsSimulatingRight = false; } else { if (KeyInputSystem.GetKeyDown(profile.LeftControllerManipulationKey)) { IsSimulatingLeft = true; if (lastSimulationLeft > 0.0f && time - lastSimulationLeft <= profile.DoublePressTime) { IsAlwaysVisibleLeft = !IsAlwaysVisibleLeft; } lastSimulationLeft = time; } if (KeyInputSystem.GetKeyUp(profile.LeftControllerManipulationKey)) { IsSimulatingLeft = false; } if (KeyInputSystem.GetKeyDown(profile.RightControllerManipulationKey)) { IsSimulatingRight = true; if (lastSimulationRight > 0.0f && time - lastSimulationRight <= profile.DoublePressTime) { IsAlwaysVisibleRight = !IsAlwaysVisibleRight; } lastSimulationRight = time; } if (KeyInputSystem.GetKeyUp(profile.RightControllerManipulationKey)) { IsSimulatingRight = false; } if (isSimulatingGaze) { lastSimulationGaze = time; } } mouseRotation.Update(profile.ControllerRotateButton, cancelRotationKey, false); SimulateInput(ref lastInputTrackedTimestampLeft, InputStateLeft, IsSimulatingLeft, IsAlwaysVisibleLeft, mouseDelta, mouseRotation.IsRotating); SimulateInput(ref lastInputTrackedTimestampRight, InputStateRight, IsSimulatingRight, IsAlwaysVisibleRight, mouseDelta, mouseRotation.IsRotating); SimulateInput(ref lastInputTrackedTimestampGaze, InputStateGaze, isSimulatingGaze, false, mouseDelta, mouseRotation.IsRotating); } /// Apply changes to one controller and update tracking internal abstract void SimulateInput( ref long lastHandTrackedTimestamp, SimulatedControllerState state, bool isSimulating, bool isAlwaysVisible, MouseDelta mouseDelta, bool useMouseRotation); /// /// Reset the controller to its default state. /// public void ResetInput(Handedness handedness) { if (handedness == Handedness.Left) { ResetInput(InputStateLeft, IsSimulatingLeft); } else { ResetInput(InputStateRight, IsSimulatingRight); } } internal virtual void ResetInput(SimulatedControllerState state, bool isSimulating) { if (isSimulating) { // Start at current mouse position Vector3 mousePos = UnityEngine.Input.mousePosition; state.ResetPosition(CameraCache.Main.ScreenToViewportPoint(new Vector3(mousePos.x, mousePos.y, profile.DefaultControllerDistance))); } else { state.ResetPosition(new Vector3(0.5f, 0.5f, profile.DefaultControllerDistance)); } } } }