File size: 14,008 Bytes
18a519f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 | 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;
}
}
}
|