예제 #1
0
        protected int RegisterTtpAngleAxis()
        {
            AxisValueCalculator calculator;

            calculator = delegate
            {
                if (!TwoPoint)
                {
                    return(0);
                }
                float2 p0    = GetPosition(TouchPoints.Touchpoint_0);
                float2 p1    = GetPosition(TouchPoints.Touchpoint_1);
                float2 delta = p1 - p0;
                float  angle = (float)System.Math.Atan2(-delta.y, delta.x); // flip y direction (up is positive) so angle values are "maths-conform".
                return(angle);
            };

            int id = NewAxisID;

            var calculatedAxisDesc = new AxisDescription
            {
                Id             = id,
                Name           = "Double-Touch Angle",
                Direction      = AxisDirection.Unknown,
                Nature         = AxisNature.Position,
                Bounded        = AxisBoundedType.Constant,
                MaxValueOrAxis = (float)System.Math.PI,
                MinValueOrAxis = -(float)System.Math.PI
            };

            RegisterCalculatedAxis(calculatedAxisDesc, calculator);
            return(calculatedAxisDesc.Id);
        }
예제 #2
0
        protected int RegisterTtpDistanceAxis()
        {
            AxisValueCalculator calculator;

            calculator = delegate
            {
                if (!TwoPoint)
                {
                    return(0);
                }
                float2 p0       = GetPosition(TouchPoints.Touchpoint_0);
                float2 p1       = GetPosition(TouchPoints.Touchpoint_1);
                float  distance = (p1 - p0).Length;
                return(distance);
            };

            int id = NewAxisID;

            var calculatedAxisDesc = new AxisDescription
            {
                Id        = id,
                Name      = "Double-Touch Distance",
                Direction = AxisDirection.Unknown,
                Nature    = AxisNature.Position,
                Bounded   = AxisBoundedType.Unbound,
                // TODO: Set min and max axes to 0 and window diagonal as bounding axes.
            };

            RegisterCalculatedAxis(calculatedAxisDesc, calculator);
            return(calculatedAxisDesc.Id);
        }
예제 #3
0
        protected int RegisterTtpMidpointAxis(int axId0, int axId1, int axIdMin, int axIdMax, AxisDirection dir, string name)
        {
            AxisValueCalculator calculator;

            calculator = delegate
            {
                if (!TwoPoint)
                {
                    return(0);
                }
                float v0       = GetAxis(axId0);
                float v1       = GetAxis(axId1);
                float midpoint = (v0 + v1) * 0.5f;
                return(midpoint);
            };

            int id = NewAxisID;

            var calculatedAxisDesc = new AxisDescription
            {
                Id             = id,
                Name           = name,
                Direction      = dir,
                Nature         = AxisNature.Position,
                Bounded        = AxisBoundedType.OtherAxis,
                MinValueOrAxis = axIdMin,
                MaxValueOrAxis = axIdMax
            };

            RegisterCalculatedAxis(calculatedAxisDesc, calculator);
            return(calculatedAxisDesc.Id);
        }
예제 #4
0
        /// <summary>
        /// Sets the deadzone for the given axis.
        /// </summary>
        /// <param name="axisId">The axis' Id as specified in <see cref="AxisDesc"/>.</param>
        /// <param name="value"></param>
        public void SetAxisDeadzone(int axisId, float value)
        {
            AxisDescription desc = GetAxisDescription(axisId);

            desc.Deadzone = value;
            _axes[axisId] = desc;
        }
예제 #5
0
        // -------------------
        public void InitDialog(InputRig rig)
        {
            this.rig = rig;

            // Add axes...

            UnityInputManagerUtils.InputAxisList inputAxisList = UnityInputManagerUtils.LoadInputManagerAxes();

            for (int i = 0; i < inputAxisList.Count; ++i)
            {
                if (inputAxisList[i].name.StartsWith("cf"))
                {
                    continue;
                }

                UnityInputManagerUtils.InputAxis inputAxis = inputAxisList[i];

                AxisDescription axisDesc = this.FindDescription(inputAxis);

                if (axisDesc == null)
                {
                    axisDesc = new AxisDescription(inputAxis);

                    int axisId = 0;

                    axisDesc.enabled           = true;
                    axisDesc.wasAlreadyDefined = rig.IsAxisDefined(inputAxis.name, ref axisId);

                    if (inputAxis.name.Equals("Submit", System.StringComparison.OrdinalIgnoreCase) ||
                        inputAxis.name.Equals("Cancel", System.StringComparison.OrdinalIgnoreCase))
                    {
                        axisDesc.enabled = false;
                    }


                    this.axisList.Add(axisDesc);
                }
                else
                {
                    axisDesc.inputAxes.Add(inputAxis);
                }
            }
        }
예제 #6
0
        /// <summary>
        /// Registers a calculated axis. Calculated axes behave like axes exposed by the underlying
        /// hardware device but can be calculated from one or more existing axes or buttons.
        /// </summary>
        /// <param name="calculatedAxisDescription">The axis description for the new calculated axis.</param>
        /// <param name="calculator">The calculator method performing the calculation once per frame.</param>
        /// <param name="initialValue">The initial value for the new axis.</param>
        /// <remarks>
        /// To register your own axis you need to provide a working <see cref="AxisValueCalculator"/>. This method
        /// is called whenever the axis value needs to be present.
        /// Any state the calculation depends upon should be queried from existing axes presented by the input device
        /// or "statically" stored in the closure around the calculator. The methodes
        /// <list type="bullet"></list>
        /// <item><see cref="RegisterSingleButtonAxis"/></item>
        /// <item><see cref="RegisterTwoButtonAxis"/></item>
        /// <item><see cref="RegisterVelocityAxis"/></item>
        /// </remarks>
        /// provide pre-defined calculators for certain purposes.
        public void RegisterCalculatedAxis(AxisDescription calculatedAxisDescription, AxisValueCalculator calculator, float initialValue = 0)
        {
            if (calculatedAxisDescription.Id < _nextAxisId)
            {
                throw new InvalidOperationException($"Invalid Id for calculated axis '{calculatedAxisDescription.Name}'. Id must be bigger or equal to {_nextAxisId}.");
            }

            _nextAxisId = calculatedAxisDescription.Id;


            var calculatedAxis = new CalculatedAxisDescription
            {
                AxisDesc         = calculatedAxisDescription,
                CurrentAxisValue = initialValue,
                Calculator       = calculator
            };

            // Calculated Axes are always to-be-polled axes.
            _calculatedAxes[calculatedAxisDescription.Id] = calculatedAxis;
            _axesToPoll[calculatedAxisDescription.Id]     = calculatedAxis.CurrentAxisValue;

            _axes[calculatedAxisDescription.Id] = calculatedAxisDescription;
        }
예제 #7
0
        // ----------------
        private void AddAxesToRig()
        {
            // Check for already present axes...

            int    enabledAxisNum        = 0;
            int    alreadyPresentAxisNum = 0;
            string presentAxisNames      = "";

            for (int i = 0; i < this.axisList.Count; ++i)
            {
                if (!this.axisList[i].enabled)
                {
                    continue;
                }

                enabledAxisNum++;

                int axisId = 0;
                if (this.rig.IsAxisDefined(this.axisList[i].name, ref axisId))
                {
                    ++alreadyPresentAxisNum;

                    if (alreadyPresentAxisNum <= 10)
                    {
                        presentAxisNames += (((alreadyPresentAxisNum == 10) ? "..." : this.axisList[i].name) + "\n");
                    }
                }
            }

            bool overwriteAll      = false;
            bool igonrePresentAxes = false;

            if (alreadyPresentAxisNum > 0)
            {
                int overwriteMethod = EditorUtility.DisplayDialogComplex(DIALOG_TITLE, "" + alreadyPresentAxisNum + " out of " + enabledAxisNum +
                                                                         " selected axes are already present in selected Input Rig.\n\n" +
                                                                         presentAxisNames + "\n" + "What do you want to do with them?", "Overwrite All", "Ignore All", "Choose one by one");


                if (overwriteMethod == 1)
                {
                    igonrePresentAxes = true;
                }

                else if (overwriteMethod == 0)
                {
                    overwriteAll = true;
                }
            }


            // Apply...

            CFGUI.CreateUndo("Transfer axes from Input Manager to Input Rig", this.rig);

            for (int i = 0; i < this.axisList.Count; ++i)
            {
                AxisDescription axisDesc = this.axisList[i];
                if (!axisDesc.enabled)
                {
                    continue;
                }



                InputRig.AxisConfig axisConfig = this.rig.GetAxisConfig(axisDesc.name);

                if (axisConfig != null)
                {
                    if (igonrePresentAxes)
                    {
                        continue;
                    }

                    if (!overwriteAll && !EditorUtility.DisplayDialog(DIALOG_TITLE, "Transfer and overwrite [" + axisDesc.name + "] axis?", "Transfer", "Skip"))
                    {
                        continue;
                    }

                    axisConfig.axisType = axisDesc.targetAxisType;
                }
                else
                {
                    axisConfig = this.rig.axes.Add(axisDesc.name, axisDesc.targetAxisType, false);
                }


                axisConfig.keyboardNegative     = KeyCode.None;
                axisConfig.keyboardNegativeAlt0 = KeyCode.None;
                axisConfig.keyboardPositive     = KeyCode.None;
                axisConfig.keyboardPositiveAlt0 = KeyCode.None;

                axisConfig.scale = 1;
                axisConfig.digitalToAnalogDecelTime = 0;
                axisConfig.digitalToAnalogAccelTime = 0;
                axisConfig.smoothingTime            = 0;
                axisConfig.rawSmoothingTime         = 0;
                axisConfig.snap = false;

                for (int ai = 0; ai < axisDesc.inputAxes.Count; ++ai)
                {
                    UnityInputManagerUtils.InputAxis inputAxis = axisDesc.inputAxes[ai];

                    switch (inputAxis.type)
                    {
                    case UnityInputManagerUtils.AxisType.KeyOrMouseButton:
                        KeyCode
                            positiveCode    = InputRig.NameToKeyCode(!inputAxis.invert ? inputAxis.positiveButton : inputAxis.negativeButton),
                            positiveAltCode = InputRig.NameToKeyCode(!inputAxis.invert ? inputAxis.altPositiveButton : inputAxis.altNegativeButton),
                            negativeCode    = InputRig.NameToKeyCode(!inputAxis.invert ? inputAxis.negativeButton : inputAxis.positiveButton),
                            negativeAltCode = InputRig.NameToKeyCode(!inputAxis.invert ? inputAxis.altNegativeButton : inputAxis.altPositiveButton);

                        if ((positiveCode != KeyCode.None) && !UnityInputManagerUtils.IsJoystickKeyCode(positiveCode))
                        {
                            axisConfig.keyboardPositive    = positiveCode;
                            axisConfig.affectedKeyPositive = positiveCode;
                        }
                        if ((positiveAltCode != KeyCode.None) && !UnityInputManagerUtils.IsJoystickKeyCode(positiveAltCode))
                        {
                            axisConfig.keyboardPositiveAlt0 = positiveAltCode;
                        }

                        if ((negativeCode != KeyCode.None) && !UnityInputManagerUtils.IsJoystickKeyCode(negativeCode))
                        {
                            axisConfig.keyboardNegative    = negativeCode;
                            axisConfig.affectedKeyNegative = negativeCode;
                        }
                        if ((negativeAltCode != KeyCode.None) && !UnityInputManagerUtils.IsJoystickKeyCode(negativeAltCode))
                        {
                            axisConfig.keyboardNegativeAlt0 = negativeAltCode;
                        }


                        if (inputAxis.snap)
                        {
                            axisConfig.snap = true;
                        }

                        break;


                    case UnityInputManagerUtils.AxisType.JoystickAxis:
                        break;


                    case UnityInputManagerUtils.AxisType.MouseMovement:
                    {
                        // Mouse Delta...

                        if ((inputAxis.axis == UnityInputManagerUtils.MOUSE_X_AXIS_ID) ||
                            (inputAxis.axis == UnityInputManagerUtils.MOUSE_Y_AXIS_ID))
                        {
                            ControlFreak2.Internal.AxisBinding mouseDeltaBinding = (inputAxis.axis == UnityInputManagerUtils.MOUSE_X_AXIS_ID) ?
                                                                                   this.rig.mouseConfig.horzDeltaBinding : this.rig.mouseConfig.vertDeltaBinding;

                            mouseDeltaBinding.Clear();
                            mouseDeltaBinding.Enable();                                                   //.enabled = true;
                            mouseDeltaBinding.AddTarget().SetSingleAxis(axisDesc.name, inputAxis.invert); //, axisDesc.inputAxis.invert);

                            //mouseDeltaBinding.separateAxes = false;
                            //mouseDeltaBinding.singleAxis = axisDesc.name;

                            axisConfig.scale = inputAxis.sensitivity;
                        }

                        // Scroll wheel...

                        else if ((inputAxis.axis == UnityInputManagerUtils.SCROLL_PRIMARY_AXIS_ID) ||
                                 (inputAxis.axis == UnityInputManagerUtils.SCROLL_SECONDARY_AXIS_ID))
                        {
                            ControlFreak2.Internal.AxisBinding scrollBinding = (inputAxis.axis == UnityInputManagerUtils.SCROLL_PRIMARY_AXIS_ID) ?
                                                                               this.rig.scrollWheel.vertScrollDeltaBinding.deltaBinding : this.rig.scrollWheel.horzScrollDeltaBinding.deltaBinding;

                            scrollBinding.Clear();
                            scrollBinding.AddTarget().SetSingleAxis(axisDesc.name, inputAxis.invert);

                            //scrollBinding.enabled = true;
                            //scrollBinding.separateAxes = false;
                            //scrollBinding.singleAxis = axisDesc.name;
                        }
                    }
                    break;
                    }
                }

                // Set mouse delta scaling...

                if (axisDesc.targetAxisType == InputRig.AxisType.Delta)
                {
                    axisConfig.deltaMode = InputRig.DeltaTransformMode.EmulateMouse;
                    axisConfig.scale     = axisDesc.inputAxis.sensitivity;
                }

                // Convert gravity to smoothing time...

                else if ((axisDesc.targetAxisType == InputRig.AxisType.SignedAnalog) || (axisDesc.targetAxisType == InputRig.AxisType.UnsignedAnalog))
                {
                    float
                        gravity     = 0,
                        sensitivity = 0;

                    // Find biggest gravity and sensitivity...

                    for (int di = 0; di < axisDesc.inputAxes.Count; ++di)
                    {
                        if (axisDesc.inputAxes[di].type != UnityInputManagerUtils.AxisType.KeyOrMouseButton)
                        {
                            continue;
                        }

                        gravity     = Mathf.Max(gravity, axisDesc.inputAxes[di].gravity);
                        sensitivity = Mathf.Max(sensitivity, axisDesc.inputAxes[di].sensitivity);
                    }

                    // Convert graivty and sensitivity to digiAccel/Decel times...

                    axisConfig.digitalToAnalogDecelTime = ((gravity < 0.001f)               ? 0.2f : (1.0f / gravity));
                    axisConfig.digitalToAnalogAccelTime = ((sensitivity < 0.001f)   ? 0.2f : (1.0f / sensitivity));

                    axisConfig.smoothingTime    = 0;
                    axisConfig.rawSmoothingTime = 0;

                    //if (axisDesc.inputAxis.gravity > 0.1f)
                    //	axisConfig.smoothingTime = Mathf.Min(1.0f, (1.0f / axisDesc.inputAxis.gravity));
                }


                //if (axisDesc.inputAxis.invert)
                //	axisConfig.scale = -axisConfig.scale;
            }

            CFGUI.EndUndo(this.rig);
        }
예제 #8
0
        /// <summary>
        /// Registers a calculated axis from two buttons. The axis' value changes between -1 and 1 as the user hits the button or releases it.
        /// The time it takes to change the value can be set.
        /// </summary>
        /// <param name="origButtonIdNegative">The original button identifier for negative movements.</param>
        /// <param name="origButtonIdPositive">The original button identifier for positive movements.</param>
        /// <param name="direction">The direction the new axis is heading towards.</param>
        /// <param name="rampUpTime">The time it takes to change the value from 0 to 1 (or -1) (in seconds) when one of the buttons is pushed.</param>
        /// <param name="rampDownTime">The time it takes to change the value from -1 of 1 back to 0 (in seconds) when a pushed button is released.</param>
        /// <param name="buttonAxisId">The new identifier of the button axis. Note this value must be bigger than all existing axis Ids. Leave this value
        /// zero to have a new identifier calculated automatically.</param>
        /// <param name="name">The name of the new axis.</param>
        /// <returns>
        /// The axis description of the newly created calculated axis.
        /// </returns>
        /// <remarks>
        /// Button axes are useful to simulate one axis of a joypad or a joystick with the help of two individual buttons. One button acts as pushing the
        /// joystick into the positve direction along the given axis by animating the axis' value to 1 and the a second button acts as pushing the joystick
        /// into the negative direction by animating the value to -1. Releasing both buttons will animate the value to 0. Pushing both buttons simultaneously
        /// will stop the animation and keep the value at its current amount.
        /// There is a user-defineable acceleration and deceleration period, so a simulation resulting on this input delivers a feeling of inertance.
        /// </remarks>
        public AxisDescription RegisterTwoButtonAxis(int origButtonIdNegative, int origButtonIdPositive, AxisDirection direction = AxisDirection.Unknown, float rampUpTime = 0.15f, float rampDownTime = 0.35f, int buttonAxisId = 0, string name = null)
        {
            ButtonDescription origButtonDescPos;

            if (!_buttons.TryGetValue(origButtonIdPositive, out origButtonDescPos))
            {
                throw new InvalidOperationException($"Button Id {origButtonIdPositive} is not known. Cannot register button axis based on unknown button.");
            }

            ButtonDescription origButtonDescNeg;

            if (!_buttons.TryGetValue(origButtonIdNegative, out origButtonDescNeg))
            {
                throw new InvalidOperationException($"Button Id {origButtonIdNegative} is not known. Cannot register button axis based on unknown button.");
            }


            // Ramp cannot be 90° as this would require special case handling
            if (rampUpTime <= float.MinValue)
            {
                rampUpTime = 2 * float.MinValue;
            }
            if (rampDownTime <= float.MinValue)
            {
                rampDownTime = 2 * float.MinValue;
            }


            bool  closureLastBtnStatePos   = GetButton(origButtonIdPositive);
            bool  closureLastBtnStateNeg   = GetButton(origButtonIdNegative);
            float closureLastAxisValue     = 0;
            float closureAnimDirection     = 0;
            AxisValueCalculator calculator = delegate(float deltaTime)
            {
                float ret;
                bool  newBtnStatePos = GetButton(origButtonIdPositive);
                if (newBtnStatePos != closureLastBtnStatePos)
                {
                    // The state of the button has changed
                    closureAnimDirection   = (newBtnStatePos ? 1 : 0) - (closureLastBtnStatePos ? 1 : 0);
                    closureLastBtnStatePos = newBtnStatePos;
                }

                bool newBtnStateNeg = GetButton(origButtonIdNegative);
                if (newBtnStateNeg != closureLastBtnStateNeg)
                {
                    // The state of the button has changed
                    closureAnimDirection   = -((newBtnStateNeg ? 1 : 0) - (closureLastBtnStateNeg ? 1 : 0));
                    closureLastBtnStateNeg = newBtnStateNeg;
                }

                // No button pressed: Goal is 0 (middle) and speed is rampdown
                var animGoal = 0.0f;
                var animTime = rampDownTime;
                if (newBtnStatePos || newBtnStateNeg)
                {
                    // Some button pressed: Goal is -1 or 1 and speed is rampup
                    animGoal = closureAnimDirection;
                    animTime = rampUpTime;
                }

                if (closureAnimDirection > 0)
                {
                    ret = closureLastAxisValue + deltaTime / animTime;
                    if (ret >= animGoal)
                    {
                        closureAnimDirection = 0;
                        ret = animGoal;
                    }
                }
                else if (closureAnimDirection < 0)
                {
                    ret = closureLastAxisValue - deltaTime / animTime;
                    if (ret <= animGoal)
                    {
                        closureAnimDirection = 0;
                        ret = animGoal;
                    }
                }
                else
                {
                    ret = closureLastAxisValue;
                }

                closureLastAxisValue = ret;
                return(ret);
            };

            int id = _nextAxisId + 1;

            if (buttonAxisId > id)
            {
                id = buttonAxisId;
            }

            var calculatedAxisDesc = new AxisDescription
            {
                Id = id, Name = name ?? $"{origButtonDescPos.Name} {origButtonDescNeg.Name} Axis", Bounded = AxisBoundedType.Constant, Direction = direction, Nature = AxisNature.Speed, MaxValueOrAxis = 1.0f, MinValueOrAxis = -1.0f
            };

            RegisterCalculatedAxis(calculatedAxisDesc, calculator);
            return(calculatedAxisDesc);
        }
예제 #9
0
        /// <summary>
        /// Registers a calculated axis from a button. The axis' value changes between 0 and 1 as the user hits the button or releases it.
        /// The time it takes to change the value can be set.
        /// </summary>
        /// <param name="origButtonId">The original button identifier.</param>
        /// <param name="direction">The direction the new axis is heading towards.</param>
        /// <param name="rampUpTime">The time it takes to change the value from 0 to 1 (in seconds).</param>
        /// <param name="rampDownTime">The time it takes to change the value from 1 to 0 (in seconds).</param>
        /// <param name="buttonAxisId">The new identifier of the button axis. Note this value must be bigger than all existing axis Ids. Leave this value
        /// zero to have a new identifier calculated automatically.</param>
        /// <param name="name">The name of the new axis.</param>
        /// <returns>The axis description of the newly created calculated axis.</returns>
        /// <remarks>
        ///   Button axes are useful to simulate a trigger or thrust panel with the help of individual buttons. There is a user-defineable acceleration and
        ///   deceleration period, so a simulation resulting on this input delivers a feeling of inertance.
        /// </remarks>
        public AxisDescription RegisterSingleButtonAxis(int origButtonId, AxisDirection direction = AxisDirection.Unknown, float rampUpTime = 0.2f, float rampDownTime = 0.2f, int buttonAxisId = 0, string name = null)
        {
            ButtonDescription origButtonDesc;

            if (!_buttons.TryGetValue(origButtonId, out origButtonDesc))
            {
                throw new InvalidOperationException($"Button Id {origButtonId} is not known. Cannot register button axis based on unknown button.");
            }

            // Ramp cannot be 90° as this would require special case handling
            if (rampUpTime <= float.MinValue)
            {
                rampUpTime = 2 * float.MinValue;
            }
            if (rampDownTime <= float.MinValue)
            {
                rampDownTime = 2 * float.MinValue;
            }

            bool  closureLastBtnState      = GetButton(origButtonId);
            float closureLastAxisValue     = 0;
            float closureAnimDirection     = 0;
            AxisValueCalculator calculator = delegate(float deltaTime)
            {
                float ret;
                bool  newBtnState = GetButton(origButtonId);
                if (newBtnState != closureLastBtnState)
                {
                    // The state of the button has changed
                    closureAnimDirection = (newBtnState ? 0 : 1) - (closureLastBtnState ? 0 : 1);
                    closureLastBtnState  = newBtnState;
                }

                if (closureAnimDirection > 0)
                {
                    ret = closureLastAxisValue + deltaTime / rampUpTime;
                    if (ret >= 1)
                    {
                        closureAnimDirection = 0;
                        ret = 1;
                    }
                }
                else if (closureAnimDirection < 0)
                {
                    ret = closureLastAxisValue - deltaTime / rampDownTime;
                    if (ret < 0)
                    {
                        closureAnimDirection = 0;
                        ret = 0;
                    }
                }
                else
                {
                    ret = closureLastAxisValue;
                }

                closureLastAxisValue = ret;
                return(ret);
            };

            int id = _nextAxisId + 1;

            if (buttonAxisId > id)
            {
                id = buttonAxisId;
            }

            var calculatedAxisDesc = new AxisDescription
            {
                Id = id, Name = name ?? $"{origButtonDesc.Name} Axis", Bounded = AxisBoundedType.Constant, Direction = direction, Nature = AxisNature.Speed, MaxValueOrAxis = 1.0f, MinValueOrAxis = 0.0f
            };

            RegisterCalculatedAxis(calculatedAxisDesc, calculator);
            return(calculatedAxisDesc);
        }
예제 #10
0
        /// <summary>
        /// Registers a calculated axis exhibiting the derivative after the time (Velocity) of the value on the specified original axis.
        /// </summary>
        /// <param name="origAxisId">The original axis identifier.</param>
        /// <param name="triggerButtonId">If a valid id is passed, the derived axis only produces values if the specified button is pressed. The velocity is only
        /// calculated based on the axis value when the trigger button is pressed. This allows touch velocities to always start with a speed of zero when the touch starts (e.g. the
        /// button identifying that a touchpoint has contact). Otherwise touch velocites would become huge between two click-like touches on different screen locations.
        /// If this parameter is 0 (zero), the derived axis will always be calculated based on the original axis only.</param>
        /// <param name="velocityAxisId">The derived axis identifier. Note this value must be bigger than all existing axis Ids. Leave this value
        /// zero to have a new identifier calculated automatically.</param>
        /// <param name="name">The name of the new axis.</param>
        /// <param name="direction">The direction of the new axis.</param>
        /// <returns>
        /// The axis description of the newly created calculated axis.
        /// </returns>
        /// <remarks>
        /// A derived axis is helpful if you have a device delivering absolute positional values but you need the current
        /// speed of the axis. Imagine a mouse where the speed of the mouse over the screen is important rather than the absolute
        /// position.
        /// </remarks>
        public AxisDescription RegisterVelocityAxis(int origAxisId, int triggerButtonId = 0,
                                                    int velocityAxisId = 0, string name = null, AxisDirection direction = AxisDirection.Unknown)
        {
            AxisDescription origAxisDesc;

            if (!_axes.TryGetValue(origAxisId, out origAxisDesc))
            {
                throw new InvalidOperationException($"Axis Id {origAxisId} is not known. Cannot register derived axis based on unknown axis.");
            }

            //switch (origAxisDesc.Bounded)
            //{
            //    case AxisBoundedType.Constant:
            //        scale = 1.0f / (origAxisDesc.MaxValueOrAxis - origAxisDesc.MinValueOrAxis);
            //        break;
            //    case AxisBoundedType.OtherAxis:
            //        scale = 1.0f / (GetAxis((int)origAxisDesc.MaxValueOrAxis) - GetAxis((int)origAxisDesc.MinValueOrAxis));
            //        break;
            //}

            AxisValueCalculator calculator;

            if (triggerButtonId != 0)
            {
                ButtonDescription triggerButtonDesc;
                if (!_buttons.TryGetValue(triggerButtonId, out triggerButtonDesc))
                {
                    throw new InvalidOperationException($"Button Id {triggerButtonId} is not known. Cannot register derived axis based on unknown trigger button id.");
                }
                float closureLastValue   = GetAxis(origAxisId);
                bool  closureOffLastTime = true;
                calculator = delegate(float deltaTime)
                {
                    if (deltaTime <= float.Epsilon) // avoid infinite velocites
                    {
                        return(0);
                    }

                    if (!GetButton(triggerButtonId))
                    {
                        closureOffLastTime = true;
                        return(0.0f);
                    }

                    if (closureOffLastTime)
                    {
                        closureLastValue   = GetAxis(origAxisId);
                        closureOffLastTime = false;
                    }

                    float newVal = GetAxis(origAxisId);
                    float ret    = (newVal - closureLastValue) / deltaTime; // v = dr / dt: velocity is position derived after time.
                    closureLastValue = newVal;
                    return(ret);
                };
            }
            else
            {
                float closureLastValue = GetAxis(origAxisId);
                calculator = delegate(float deltaTime)
                {
                    if (deltaTime <= float.Epsilon) // avoid infinite velocites
                    {
                        return(0);
                    }

                    float newVal = GetAxis(origAxisId);
                    float ret    = (newVal - closureLastValue) / deltaTime; // v = dr / dt: velocity is position derived after time.
                    closureLastValue = newVal;
                    return(ret);
                };
            }


            int id = _nextAxisId + 1;

            if (velocityAxisId > id)
            {
                id = velocityAxisId;
            }

            var calculatedAxisDesc = new AxisDescription
            {
                Id             = id,
                Name           = name ?? origAxisDesc.Name + " Velocity",
                Direction      = (direction == AxisDirection.Unknown) ? origAxisDesc.Direction : direction,
                Nature         = AxisNature.Speed,
                Bounded        = AxisBoundedType.Unbound,
                MaxValueOrAxis = float.NaN,
                MinValueOrAxis = float.NaN
            };

            RegisterCalculatedAxis(calculatedAxisDesc, calculator);
            return(calculatedAxisDesc);
        }