| using System.Runtime.CompilerServices; | |
| using UnityEngine.InputSystem.LowLevel; | |
| using UnityEngine.InputSystem.Processors; | |
| using UnityEngine.InputSystem.Utilities; | |
| ////REVIEW: change 'clampToConstant' to simply 'clampToMin'? | |
| ////TODO: if AxisControl fields where properties, we wouldn't need ApplyParameterChanges, maybe it's ok breaking change? | |
| namespace UnityEngine.InputSystem.Controls | |
| { | |
| /// <summary> | |
| /// A floating-point axis control. | |
| /// </summary> | |
| /// <remarks> | |
| /// Can optionally be configured to perform normalization. | |
| /// Stored as either a float, a short, a byte, or a single bit. | |
| /// </remarks> | |
| public class AxisControl : InputControl<float> | |
| { | |
| /// <summary> | |
| /// Clamping behavior for an axis control. | |
| /// </summary> | |
| public enum Clamp | |
| { | |
| /// <summary> | |
| /// Do not clamp values. | |
| /// </summary> | |
| None = 0, | |
| /// <summary> | |
| /// Clamp values to <see cref="clampMin"/> and <see cref="clampMax"/> | |
| /// before normalizing the value. | |
| /// </summary> | |
| BeforeNormalize = 1, | |
| /// <summary> | |
| /// Clamp values to <see cref="clampMin"/> and <see cref="clampMax"/> | |
| /// after normalizing the value. | |
| /// </summary> | |
| AfterNormalize = 2, | |
| /// <summary> | |
| /// Clamp values any value below <see cref="clampMin"/> or above <see cref="clampMax"/> | |
| /// to <see cref="clampConstant"/> before normalizing the value. | |
| /// </summary> | |
| ToConstantBeforeNormalize = 3, | |
| } | |
| // These can be added as processors but they are so common that we | |
| // build the functionality right into AxisControl to save us an | |
| // additional object and an additional virtual call. | |
| // NOTE: A number of the parameters here can be expressed in much simpler form. | |
| // E.g. 'scale', 'scaleFactor' and 'invert' could all be rolled into a single | |
| // multiplier. And maybe that's what we should do. However, the one advantage | |
| // of the current setup is that it allows to set these operations up individually. | |
| // For example, a given layout may want to have a very specific scale factor but | |
| // then a derived layout needs the value to be inverted. If it was a single setting, | |
| // the derived layout would have to know the specific scale factor in order to come | |
| // up with a valid multiplier. | |
| /// <summary> | |
| /// Clamping behavior when reading values. <see cref="Clamp.None"/> by default. | |
| /// </summary> | |
| /// <value>Clamping behavior.</value> | |
| /// <remarks> | |
| /// When a value is read from the control's state, it is first converted | |
| /// to a floating-point number. | |
| /// </remarks> | |
| /// <seealso cref="clampMin"/> | |
| /// <seealso cref="clampMax"/> | |
| /// <seealso cref="clampConstant"/> | |
| public Clamp clamp; | |
| /// <summary> | |
| /// Lower end of the clamping range when <see cref="clamp"/> is not | |
| /// <see cref="Clamp.None"/>. | |
| /// </summary> | |
| /// <value>Lower bound of clamping range. Inclusive.</value> | |
| public float clampMin; | |
| /// <summary> | |
| /// Upper end of the clamping range when <see cref="clamp"/> is not | |
| /// <see cref="Clamp.None"/>. | |
| /// </summary> | |
| /// <value>Upper bound of clamping range. Inclusive.</value> | |
| public float clampMax; | |
| /// <summary> | |
| /// When <see cref="clamp"/> is set to <see cref="Clamp.ToConstantBeforeNormalize"/> | |
| /// and the value is outside of the range defined by <see cref="clampMin"/> and | |
| /// <see cref="clampMax"/>, this value is returned. | |
| /// </summary> | |
| /// <value>Constant value to return when value is outside of clamping range.</value> | |
| public float clampConstant; | |
| ////REVIEW: why not just roll this into scaleFactor? | |
| /// <summary> | |
| /// If true, the input value will be inverted, i.e. multiplied by -1. Off by default. | |
| /// </summary> | |
| /// <value>Whether to invert the input value.</value> | |
| public bool invert; | |
| /// <summary> | |
| /// If true, normalize the input value to [0..1] or [-1..1] (depending on the | |
| /// value of <see cref="normalizeZero"/>. Off by default. | |
| /// </summary> | |
| /// <value>Whether to normalize input values or not.</value> | |
| /// <seealso cref="normalizeMin"/> | |
| /// <seealso cref="normalizeMax"/> | |
| public bool normalize; | |
| ////REVIEW: shouldn't these come from the control min/max value by default? | |
| /// <summary> | |
| /// If <see cref="normalize"/> is on, this is the input value that corresponds | |
| /// to 0 of the normalized [0..1] or [-1..1] range. | |
| /// </summary> | |
| /// <value>Input value that should become 0 or -1.</value> | |
| /// <remarks> | |
| /// In other words, with <see cref="normalize"/> on, input values are mapped from | |
| /// the range of [normalizeMin..normalizeMax] to [0..1] or [-1..1] (depending on | |
| /// <see cref="normalizeZero"/>). | |
| /// </remarks> | |
| public float normalizeMin; | |
| /// <summary> | |
| /// If <see cref="normalize"/> is on, this is the input value that corresponds | |
| /// to 1 of the normalized [0..1] or [-1..1] range. | |
| /// </summary> | |
| /// <value>Input value that should become 1.</value> | |
| /// <remarks> | |
| /// In other words, with <see cref="normalize"/> on, input values are mapped from | |
| /// the range of [normalizeMin..normalizeMax] to [0..1] or [-1..1] (depending on | |
| /// <see cref="normalizeZero"/>). | |
| /// </remarks> | |
| public float normalizeMax; | |
| /// <summary> | |
| /// Where to put the zero point of the normalization range. Only relevant | |
| /// if <see cref="normalize"/> is set to true. Defaults to 0. | |
| /// </summary> | |
| /// <value>Zero point of normalization range.</value> | |
| /// <remarks> | |
| /// The value of this property determines where the zero point is located in the | |
| /// range established by <see cref="normalizeMin"/> and <see cref="normalizeMax"/>. | |
| /// | |
| /// If <c>normalizeZero</c> is placed at <see cref="normalizeMin"/>, the normalization | |
| /// returns a value in the [0..1] range mapped from the input value range of | |
| /// <see cref="normalizeMin"/> and <see cref="normalizeMax"/>. | |
| /// | |
| /// If <c>normalizeZero</c> is placed in-between <see cref="normalizeMin"/> and | |
| /// <see cref="normalizeMax"/>, normalization returns a value in the [-1..1] mapped | |
| /// from the input value range of <see cref="normalizeMin"/> and <see cref="normalizeMax"/> | |
| /// and the zero point between the two established by <c>normalizeZero</c>. | |
| /// </remarks> | |
| public float normalizeZero; | |
| ////REVIEW: why not just have a default scaleFactor of 1? | |
| /// <summary> | |
| /// Whether the scale the input value by <see cref="scaleFactor"/>. Off by default. | |
| /// </summary> | |
| /// <value>True if inputs should be scaled by <see cref="scaleFactor"/>.</value> | |
| public bool scale; | |
| /// <summary> | |
| /// Value to multiple input values with. Only applied if <see cref="scale"/> is <c>true</c>. | |
| /// </summary> | |
| /// <value>Multiplier for input values.</value> | |
| public float scaleFactor; | |
| /// <summary> | |
| /// Apply modifications to the given value according to the parameters configured | |
| /// on the control (<see cref="clamp"/>, <see cref="normalize"/>, etc). | |
| /// </summary> | |
| /// <param name="value">Input value.</param> | |
| /// <returns>A processed value (clamped, normalized, etc).</returns> | |
| /// <seealso cref="clamp"/> | |
| /// <seealso cref="normalize"/> | |
| /// <seealso cref="scale"/> | |
| /// <seealso cref="invert"/> | |
| [] | |
| protected float Preprocess(float value) | |
| { | |
| if (scale) | |
| value *= scaleFactor; | |
| if (clamp == Clamp.ToConstantBeforeNormalize) | |
| { | |
| if (value < clampMin || value > clampMax) | |
| value = clampConstant; | |
| } | |
| else if (clamp == Clamp.BeforeNormalize) | |
| value = Mathf.Clamp(value, clampMin, clampMax); | |
| if (normalize) | |
| value = NormalizeProcessor.Normalize(value, normalizeMin, normalizeMax, normalizeZero); | |
| if (clamp == Clamp.AfterNormalize) | |
| value = Mathf.Clamp(value, clampMin, clampMax); | |
| if (invert) | |
| value *= -1.0f; | |
| return value; | |
| } | |
| private float Unpreprocess(float value) | |
| { | |
| // Does not reverse the effect of clamping (we don't know what the unclamped value should be). | |
| if (invert) | |
| value *= -1f; | |
| if (normalize) | |
| value = NormalizeProcessor.Denormalize(value, normalizeMin, normalizeMax, normalizeZero); | |
| if (scale) | |
| value /= scaleFactor; | |
| return value; | |
| } | |
| /// <summary> | |
| /// Default-initialize the control. | |
| /// </summary> | |
| /// <remarks> | |
| /// Defaults the format to <see cref="InputStateBlock.FormatFloat"/>. | |
| /// </remarks> | |
| public AxisControl() | |
| { | |
| m_StateBlock.format = InputStateBlock.FormatFloat; | |
| } | |
| protected override void FinishSetup() | |
| { | |
| base.FinishSetup(); | |
| // if we don't have any default state, and we are using normalizeZero, then the default value | |
| // should not be zero. Generate it from normalizeZero. | |
| if (!hasDefaultState && normalize && Mathf.Abs(normalizeZero) > Mathf.Epsilon) | |
| m_DefaultState = stateBlock.FloatToPrimitiveValue(normalizeZero); | |
| } | |
| /// <inheritdoc /> | |
| public override unsafe float ReadUnprocessedValueFromState(void* statePtr) | |
| { | |
| switch (m_OptimizedControlDataType) | |
| { | |
| case InputStateBlock.kFormatFloat: | |
| return *(float*)((byte*)statePtr + m_StateBlock.m_ByteOffset); | |
| case InputStateBlock.kFormatByte: | |
| return *((byte*)statePtr + m_StateBlock.m_ByteOffset) != 0 ? 1.0f : 0.0f; | |
| default: | |
| { | |
| var value = stateBlock.ReadFloat(statePtr); | |
| ////REVIEW: this isn't very raw | |
| return Preprocess(value); | |
| } | |
| } | |
| } | |
| /// <inheritdoc /> | |
| public override unsafe void WriteValueIntoState(float value, void* statePtr) | |
| { | |
| switch (m_OptimizedControlDataType) | |
| { | |
| case InputStateBlock.kFormatFloat: | |
| *(float*)((byte*)statePtr + m_StateBlock.m_ByteOffset) = value; | |
| break; | |
| case InputStateBlock.kFormatByte: | |
| *((byte*)statePtr + m_StateBlock.m_ByteOffset) = (byte)(value >= 0.5f ? 1 : 0); | |
| break; | |
| default: | |
| value = Unpreprocess(value); | |
| stateBlock.WriteFloat(statePtr, value); | |
| break; | |
| } | |
| } | |
| /// <inheritdoc /> | |
| public override unsafe bool CompareValue(void* firstStatePtr, void* secondStatePtr) | |
| { | |
| var currentValue = ReadValueFromState(firstStatePtr); | |
| var valueInState = ReadValueFromState(secondStatePtr); | |
| return !Mathf.Approximately(currentValue, valueInState); | |
| } | |
| /// <inheritdoc /> | |
| public override unsafe float EvaluateMagnitude(void* statePtr) | |
| { | |
| return EvaluateMagnitude(ReadValueFromStateWithCaching(statePtr)); | |
| } | |
| private float EvaluateMagnitude(float value) | |
| { | |
| if (m_MinValue.isEmpty || m_MaxValue.isEmpty) | |
| return Mathf.Abs(value); | |
| var min = m_MinValue.ToSingle(); | |
| var max = m_MaxValue.ToSingle(); | |
| var clampedValue = Mathf.Clamp(value, min, max); | |
| // If part of our range is in negative space, evaluate magnitude as two | |
| // separate subspaces. | |
| if (min < 0) | |
| { | |
| if (clampedValue < 0) | |
| return NormalizeProcessor.Normalize(Mathf.Abs(clampedValue), 0, Mathf.Abs(min), 0); | |
| return NormalizeProcessor.Normalize(clampedValue, 0, max, 0); | |
| } | |
| return NormalizeProcessor.Normalize(clampedValue, min, max, 0); | |
| } | |
| protected override FourCC CalculateOptimizedControlDataType() | |
| { | |
| var noProcessingNeeded = | |
| clamp == Clamp.None && | |
| invert == false && | |
| normalize == false && | |
| scale == false; | |
| if (noProcessingNeeded && | |
| m_StateBlock.format == InputStateBlock.FormatFloat && | |
| m_StateBlock.sizeInBits == 32 && | |
| m_StateBlock.bitOffset == 0) | |
| return InputStateBlock.FormatFloat; | |
| if (noProcessingNeeded && | |
| m_StateBlock.format == InputStateBlock.FormatBit && | |
| // has to be 8, otherwise we might be mapping to a state which only represents first bit in the byte, while other bits might map to some other controls | |
| // like in the mouse where LMB/RMB map to the same byte, just LMB maps to first bit and RMB maps to second bit | |
| m_StateBlock.sizeInBits == 8 && | |
| m_StateBlock.bitOffset == 0) | |
| return InputStateBlock.FormatByte; | |
| return InputStateBlock.FormatInvalid; | |
| } | |
| } | |
| } | |