/// <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); }
/// <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); }
/// <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; }