335 lines
14 KiB
C#
335 lines
14 KiB
C#
|
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"/>
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|