2023-05-07 18:43:11 -04:00
using System.Runtime.CompilerServices ;
2023-03-28 13:24:16 -04:00
using UnityEngine.InputSystem.LowLevel ;
using UnityEngine.InputSystem.Processors ;
2023-05-07 18:43:11 -04:00
using UnityEngine.InputSystem.Utilities ;
2023-03-28 13:24:16 -04:00
////REVIEW: change 'clampToConstant' to simply 'clampToMin'?
2023-05-07 18:43:11 -04:00
////TODO: if AxisControl fields where properties, we wouldn't need ApplyParameterChanges, maybe it's ok breaking change?
2023-03-28 13:24:16 -04:00
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"/>
2023-05-07 18:43:11 -04:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2023-03-28 13:24:16 -04:00
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 )
{
2023-05-07 18:43:11 -04:00
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 ) ;
}
}
2023-03-28 13:24:16 -04:00
}
/// <inheritdoc />
public override unsafe void WriteValueIntoState ( float value , void * statePtr )
{
2023-05-07 18:43:11 -04:00
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 ;
}
2023-03-28 13:24:16 -04:00
}
/// <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 )
{
2023-05-07 18:43:11 -04:00
return EvaluateMagnitude ( ReadValueFromStateWithCaching ( statePtr ) ) ;
}
private float EvaluateMagnitude ( float value )
{
2023-03-28 13:24:16 -04:00
if ( m_MinValue . isEmpty | | m_MaxValue . isEmpty )
return Mathf . Abs ( value ) ;
var min = m_MinValue . ToSingle ( ) ;
var max = m_MaxValue . ToSingle ( ) ;
2023-05-07 18:43:11 -04:00
var clampedValue = Mathf . Clamp ( value , min , max ) ;
2023-03-28 13:24:16 -04:00
// If part of our range is in negative space, evaluate magnitude as two
// separate subspaces.
if ( min < 0 )
{
2023-05-07 18:43:11 -04:00
if ( clampedValue < 0 )
return NormalizeProcessor . Normalize ( Mathf . Abs ( clampedValue ) , 0 , Mathf . Abs ( min ) , 0 ) ;
return NormalizeProcessor . Normalize ( clampedValue , 0 , max , 0 ) ;
2023-03-28 13:24:16 -04:00
}
2023-05-07 18:43:11 -04:00
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 ;
2023-03-28 13:24:16 -04:00
}
}
}