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
{
///
/// A floating-point axis control.
///
///
/// Can optionally be configured to perform normalization.
/// Stored as either a float, a short, a byte, or a single bit.
///
public class AxisControl : InputControl
{
///
/// Clamping behavior for an axis control.
///
public enum Clamp
{
///
/// Do not clamp values.
///
None = 0,
///
/// Clamp values to and
/// before normalizing the value.
///
BeforeNormalize = 1,
///
/// Clamp values to and
/// after normalizing the value.
///
AfterNormalize = 2,
///
/// Clamp values any value below or above
/// to before normalizing the value.
///
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.
///
/// Clamping behavior when reading values. by default.
///
/// Clamping behavior.
///
/// When a value is read from the control's state, it is first converted
/// to a floating-point number.
///
///
///
///
public Clamp clamp;
///
/// Lower end of the clamping range when is not
/// .
///
/// Lower bound of clamping range. Inclusive.
public float clampMin;
///
/// Upper end of the clamping range when is not
/// .
///
/// Upper bound of clamping range. Inclusive.
public float clampMax;
///
/// When is set to
/// and the value is outside of the range defined by and
/// , this value is returned.
///
/// Constant value to return when value is outside of clamping range.
public float clampConstant;
////REVIEW: why not just roll this into scaleFactor?
///
/// If true, the input value will be inverted, i.e. multiplied by -1. Off by default.
///
/// Whether to invert the input value.
public bool invert;
///
/// If true, normalize the input value to [0..1] or [-1..1] (depending on the
/// value of . Off by default.
///
/// Whether to normalize input values or not.
///
///
public bool normalize;
////REVIEW: shouldn't these come from the control min/max value by default?
///
/// If is on, this is the input value that corresponds
/// to 0 of the normalized [0..1] or [-1..1] range.
///
/// Input value that should become 0 or -1.
///
/// In other words, with on, input values are mapped from
/// the range of [normalizeMin..normalizeMax] to [0..1] or [-1..1] (depending on
/// ).
///
public float normalizeMin;
///
/// If is on, this is the input value that corresponds
/// to 1 of the normalized [0..1] or [-1..1] range.
///
/// Input value that should become 1.
///
/// In other words, with on, input values are mapped from
/// the range of [normalizeMin..normalizeMax] to [0..1] or [-1..1] (depending on
/// ).
///
public float normalizeMax;
///
/// Where to put the zero point of the normalization range. Only relevant
/// if is set to true. Defaults to 0.
///
/// Zero point of normalization range.
///
/// The value of this property determines where the zero point is located in the
/// range established by and .
///
/// If normalizeZero is placed at , the normalization
/// returns a value in the [0..1] range mapped from the input value range of
/// and .
///
/// If normalizeZero is placed in-between and
/// , normalization returns a value in the [-1..1] mapped
/// from the input value range of and
/// and the zero point between the two established by normalizeZero.
///
public float normalizeZero;
////REVIEW: why not just have a default scaleFactor of 1?
///
/// Whether the scale the input value by . Off by default.
///
/// True if inputs should be scaled by .
public bool scale;
///
/// Value to multiple input values with. Only applied if is true.
///
/// Multiplier for input values.
public float scaleFactor;
///
/// Apply modifications to the given value according to the parameters configured
/// on the control (, , etc).
///
/// Input value.
/// A processed value (clamped, normalized, etc).
///
///
///
///
[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;
}
///
/// Default-initialize the control.
///
///
/// Defaults the format to .
///
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);
}
///
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);
}
}
}
///
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;
}
}
///
public override unsafe bool CompareValue(void* firstStatePtr, void* secondStatePtr)
{
var currentValue = ReadValueFromState(firstStatePtr);
var valueInState = ReadValueFromState(secondStatePtr);
return !Mathf.Approximately(currentValue, valueInState);
}
///
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;
}
}
}