/// Apply changes to one hand and update tracking
        private void SimulateHandInput(
            ref long lastSimulatedTimestamp,
            SimulatedHandState state,
            bool isSimulating,
            bool isAlwaysVisible,
            Vector3 mouseDelta,
            Vector3 rotationDeltaEulerAngles)
        {
            bool enableTracking = isAlwaysVisible || isSimulating;

            if (!state.IsTracked && enableTracking)
            {
                // Start at current mouse position
                Vector3 mousePos = UnityEngine.Input.mousePosition;
                state.ScreenPosition = new Vector3(mousePos.x, mousePos.y, profile.DefaultHandDistance);
            }

            if (isSimulating)
            {
                state.SimulateInput(mouseDelta, profile.HandJitterAmount, rotationDeltaEulerAngles);

                if (isAlwaysVisible)
                {
                    // Toggle gestures on/off
                    state.Gesture = ToggleGesture(state.Gesture);
                }
                else
                {
                    // Enable gesture while mouse button is pressed
                    state.Gesture = SelectGesture();
                }
            }

            // Update tracked state of a hand.
            // If hideTimeout value is null, hands will stay visible after tracking stops.
            // TODO: DateTime.UtcNow can be quite imprecise, better use Stopwatch.GetTimestamp
            // https://stackoverflow.com/questions/2143140/c-sharp-datetime-now-precision
            DateTime currentTime = DateTime.UtcNow;

            if (enableTracking)
            {
                state.IsTracked        = true;
                lastSimulatedTimestamp = currentTime.Ticks;
            }
            else
            {
                float timeSinceTracking = (float)currentTime.Subtract(new DateTime(lastSimulatedTimestamp)).TotalSeconds;
                if (timeSinceTracking > profile.HandHideTimeout)
                {
                    state.IsTracked = false;
                }
            }
        }
        /// Apply changes to one hand and update tracking
        private void SimulateHandInput(
            ref long lastHandTrackedTimestamp,
            SimulatedHandState state,
            bool isSimulating,
            bool isAlwaysVisible,
            MouseDelta mouseDelta,
            bool useMouseRotation)
        {
            bool enableTracking = isAlwaysVisible || isSimulating;

            if (!state.IsTracked && enableTracking)
            {
                ResetHand(state, isSimulating);
            }

            if (isSimulating)
            {
                state.SimulateInput(mouseDelta, useMouseRotation, profile.MouseRotationSensitivity, profile.MouseHandRotationSpeed, profile.HandJitterAmount);

                if (isAlwaysVisible)
                {
                    // Toggle gestures on/off
                    state.Gesture = ToggleGesture(state.Gesture);
                }
                else
                {
                    // Enable gesture while mouse button is pressed
                    state.Gesture = SelectGesture();
                }
            }

            // Update tracked state of a hand.
            // If hideTimeout value is null, hands will stay visible after tracking stops.
            // TODO: DateTime.UtcNow can be quite imprecise, better use Stopwatch.GetTimestamp
            // https://stackoverflow.com/questions/2143140/c-sharp-datetime-now-precision
            DateTime currentTime = DateTime.UtcNow;

            if (enableTracking)
            {
                state.IsTracked          = true;
                lastHandTrackedTimestamp = currentTime.Ticks;
            }
            else
            {
                float timeSinceTracking = (float)currentTime.Subtract(new DateTime(lastHandTrackedTimestamp)).TotalSeconds;
                if (timeSinceTracking > profile.HandHideTimeout)
                {
                    state.IsTracked = false;
                }
            }
        }
        public void Update(bool enable)
        {
            if (!enable)
            {
                return;
            }

            bool wasLeftVisible  = stateLeft.IsVisible;
            bool wasRightVisible = stateRight.IsVisible;

            if (UnityEngine.Input.GetKeyDown(profile.ToggleLeftHandKey))
            {
                stateLeft.IsAlwaysTracked = !stateLeft.IsAlwaysTracked;
            }
            if (UnityEngine.Input.GetKeyDown(profile.ToggleRightHandKey))
            {
                stateRight.IsAlwaysTracked = !stateRight.IsAlwaysTracked;
            }

            if (UnityEngine.Input.GetKeyDown(profile.LeftHandManipulationKey))
            {
                stateLeft.IsSimulated = true;
                HandleCursor();
            }
            if (UnityEngine.Input.GetKeyUp(profile.LeftHandManipulationKey))
            {
                stateLeft.IsSimulated = false;
                HandleCursor();
            }

            if (UnityEngine.Input.GetKeyDown(profile.RightHandManipulationKey))
            {
                stateRight.IsSimulated = true;
                HandleCursor();
            }
            if (UnityEngine.Input.GetKeyUp(profile.RightHandManipulationKey))
            {
                stateRight.IsSimulated = false;
                HandleCursor();
            }

            // Hide cursor if either of the hands is simulated

            /*
             * Cursor.visible = !stateLeft.IsSimulated && !stateRight.IsSimulated;
             * Cursor.lockState = (!Cursor.visible) ? CursorLockMode.Locked : CursorLockMode.None;
             */

            stateLeft.UpdateVisibility(profile.HandHideTimeout);
            stateRight.UpdateVisibility(profile.HandHideTimeout);
            // Reset when enabling
            if (!wasLeftVisible && stateLeft.IsVisible)
            {
                stateLeft.Reset(profile.DefaultHandDistance, profile.DefaultHandGesture);
            }
            if (!wasRightVisible && stateRight.IsVisible)
            {
                stateRight.Reset(profile.DefaultHandDistance, profile.DefaultHandGesture);
            }

            var mousePos = MoveSpeed * new Vector3(UnityEngine.Input.GetAxis("Mouse X"), UnityEngine.Input.GetAxis("Mouse Y"), 0);

            Vector3 mouseDelta = mousePos;

            mouseDelta.z += UnityEngine.Input.GetAxis("Mouse ScrollWheel") * profile.HandDepthMultiplier;
            float   rotationDelta            = profile.HandRotationSpeed * Time.deltaTime;
            Vector3 rotationDeltaEulerAngles = Vector3.zero;

            if (UnityEngine.Input.GetKey(profile.YawHandCCWKey))
            {
                rotationDeltaEulerAngles.y = -rotationDelta;
            }
            if (UnityEngine.Input.GetKey(profile.YawHandCWKey))
            {
                rotationDeltaEulerAngles.y = rotationDelta;
            }
            if (UnityEngine.Input.GetKey(profile.PitchHandCCWKey))
            {
                rotationDeltaEulerAngles.x = rotationDelta;
            }
            if (UnityEngine.Input.GetKey(profile.PitchHandCWKey))
            {
                rotationDeltaEulerAngles.x = -rotationDelta;
            }
            if (UnityEngine.Input.GetKey(profile.RollHandCCWKey))
            {
                rotationDeltaEulerAngles.z = rotationDelta;
            }
            if (UnityEngine.Input.GetKey(profile.RollHandCWKey))
            {
                rotationDeltaEulerAngles.z = -rotationDelta;
            }

            stateLeft.SimulateInput(mouseDelta, profile.HandJitterAmount, rotationDeltaEulerAngles);
            stateRight.SimulateInput(mouseDelta, profile.HandJitterAmount, rotationDeltaEulerAngles);

            float gestureAnimDelta = profile.HandGestureAnimationSpeed * Time.deltaTime;

            AnimateGesture(stateLeft, gestureAnimDelta);
            AnimateGesture(stateRight, gestureAnimDelta);

            //lastMousePosition = UnityEngine.Input.mousePosition;
            //Debug.Log(lastMousePosition);
            //Debug.Log(new Vector3(UnityEngine.Input.GetAxis("Mouse X"), UnityEngine.Input.GetAxis("Mouse Y"), 0));

            ApplyHandData();
        }
        public void Update()
        {
            bool wasLeftVisible  = stateLeft.IsVisible;
            bool wasRightVisible = stateRight.IsVisible;

            if (UnityEngine.Input.GetKeyDown(profile.ToggleLeftHandKey))
            {
                stateLeft.IsAlwaysTracked = !stateLeft.IsAlwaysTracked;
            }
            if (UnityEngine.Input.GetKeyDown(profile.ToggleRightHandKey))
            {
                stateRight.IsAlwaysTracked = !stateRight.IsAlwaysTracked;
            }

            if (UnityEngine.Input.GetKeyDown(profile.LeftHandManipulationKey))
            {
                stateLeft.IsSimulated = true;
            }
            if (UnityEngine.Input.GetKeyUp(profile.LeftHandManipulationKey))
            {
                stateLeft.IsSimulated = false;
            }

            if (UnityEngine.Input.GetKeyDown(profile.RightHandManipulationKey))
            {
                stateRight.IsSimulated = true;
            }
            if (UnityEngine.Input.GetKeyUp(profile.RightHandManipulationKey))
            {
                stateRight.IsSimulated = false;
            }

            // Hide cursor if either of the hands is simulated
            Cursor.visible = !stateLeft.IsSimulated && !stateRight.IsSimulated;

            stateLeft.UpdateVisibility(profile.HandHideTimeout);
            stateRight.UpdateVisibility(profile.HandHideTimeout);
            // Reset when enabling
            if (!wasLeftVisible && stateLeft.IsVisible)
            {
                stateLeft.Reset(profile.DefaultHandDistance, profile.DefaultHandGesture);
            }
            if (!wasRightVisible && stateRight.IsVisible)
            {
                stateRight.Reset(profile.DefaultHandDistance, profile.DefaultHandGesture);
            }

            Vector3 mouseDelta = (lastMousePosition.HasValue ? UnityEngine.Input.mousePosition - lastMousePosition.Value : Vector3.zero);

            mouseDelta.z += UnityEngine.Input.GetAxis("Mouse ScrollWheel") * profile.HandDepthMultiplier;
            float   rotationDelta            = profile.HandRotationSpeed * Time.deltaTime;
            Vector3 rotationDeltaEulerAngles = Vector3.zero;

            if (UnityEngine.Input.GetKey(profile.YawHandCCWKey))
            {
                rotationDeltaEulerAngles.y = -rotationDelta;
            }
            if (UnityEngine.Input.GetKey(profile.YawHandCWKey))
            {
                rotationDeltaEulerAngles.y = rotationDelta;
            }
            if (UnityEngine.Input.GetKey(profile.PitchHandCCWKey))
            {
                rotationDeltaEulerAngles.x = rotationDelta;
            }
            if (UnityEngine.Input.GetKey(profile.PitchHandCWKey))
            {
                rotationDeltaEulerAngles.x = -rotationDelta;
            }
            if (UnityEngine.Input.GetKey(profile.RollHandCCWKey))
            {
                rotationDeltaEulerAngles.z = rotationDelta;
            }
            if (UnityEngine.Input.GetKey(profile.RollHandCWKey))
            {
                rotationDeltaEulerAngles.z = -rotationDelta;
            }
            stateLeft.SimulateInput(mouseDelta, profile.HandJitterAmount, rotationDeltaEulerAngles);
            stateRight.SimulateInput(mouseDelta, profile.HandJitterAmount, rotationDeltaEulerAngles);

            float gestureAnimDelta = profile.HandGestureAnimationSpeed * Time.deltaTime;

            AnimateGesture(stateLeft, gestureAnimDelta);
            AnimateGesture(stateRight, gestureAnimDelta);

            lastMousePosition = UnityEngine.Input.mousePosition;

            ApplyHandData();
        }