A game about forced loneliness, made by TACStudios
1using System.Runtime.CompilerServices; 2using UnityEngine.InputSystem.LowLevel; 3using UnityEngine.InputSystem.Processors; 4using UnityEngine.InputSystem.Utilities; 5 6////REVIEW: change 'clampToConstant' to simply 'clampToMin'? 7 8////TODO: if AxisControl fields where properties, we wouldn't need ApplyParameterChanges, maybe it's ok breaking change? 9 10namespace UnityEngine.InputSystem.Controls 11{ 12 /// <summary> 13 /// A floating-point axis control. 14 /// </summary> 15 /// <remarks> 16 /// Can optionally be configured to perform normalization. 17 /// Stored as either a float, a short, a byte, or a single bit. 18 /// </remarks> 19 public class AxisControl : InputControl<float> 20 { 21 /// <summary> 22 /// Clamping behavior for an axis control. 23 /// </summary> 24 public enum Clamp 25 { 26 /// <summary> 27 /// Do not clamp values. 28 /// </summary> 29 None = 0, 30 31 /// <summary> 32 /// Clamp values to <see cref="clampMin"/> and <see cref="clampMax"/> 33 /// before normalizing the value. 34 /// </summary> 35 BeforeNormalize = 1, 36 37 /// <summary> 38 /// Clamp values to <see cref="clampMin"/> and <see cref="clampMax"/> 39 /// after normalizing the value. 40 /// </summary> 41 AfterNormalize = 2, 42 43 /// <summary> 44 /// Clamp values any value below <see cref="clampMin"/> or above <see cref="clampMax"/> 45 /// to <see cref="clampConstant"/> before normalizing the value. 46 /// </summary> 47 ToConstantBeforeNormalize = 3, 48 } 49 50 // These can be added as processors but they are so common that we 51 // build the functionality right into AxisControl to save us an 52 // additional object and an additional virtual call. 53 54 // NOTE: A number of the parameters here can be expressed in much simpler form. 55 // E.g. 'scale', 'scaleFactor' and 'invert' could all be rolled into a single 56 // multiplier. And maybe that's what we should do. However, the one advantage 57 // of the current setup is that it allows to set these operations up individually. 58 // For example, a given layout may want to have a very specific scale factor but 59 // then a derived layout needs the value to be inverted. If it was a single setting, 60 // the derived layout would have to know the specific scale factor in order to come 61 // up with a valid multiplier. 62 63 /// <summary> 64 /// Clamping behavior when reading values. <see cref="Clamp.None"/> by default. 65 /// </summary> 66 /// <value>Clamping behavior.</value> 67 /// <remarks> 68 /// When a value is read from the control's state, it is first converted 69 /// to a floating-point number. 70 /// </remarks> 71 /// <seealso cref="clampMin"/> 72 /// <seealso cref="clampMax"/> 73 /// <seealso cref="clampConstant"/> 74 public Clamp clamp; 75 76 /// <summary> 77 /// Lower end of the clamping range when <see cref="clamp"/> is not 78 /// <see cref="Clamp.None"/>. 79 /// </summary> 80 /// <value>Lower bound of clamping range. Inclusive.</value> 81 public float clampMin; 82 83 /// <summary> 84 /// Upper end of the clamping range when <see cref="clamp"/> is not 85 /// <see cref="Clamp.None"/>. 86 /// </summary> 87 /// <value>Upper bound of clamping range. Inclusive.</value> 88 public float clampMax; 89 90 /// <summary> 91 /// When <see cref="clamp"/> is set to <see cref="Clamp.ToConstantBeforeNormalize"/> 92 /// and the value is outside of the range defined by <see cref="clampMin"/> and 93 /// <see cref="clampMax"/>, this value is returned. 94 /// </summary> 95 /// <value>Constant value to return when value is outside of clamping range.</value> 96 public float clampConstant; 97 98 ////REVIEW: why not just roll this into scaleFactor? 99 /// <summary> 100 /// If true, the input value will be inverted, i.e. multiplied by -1. Off by default. 101 /// </summary> 102 /// <value>Whether to invert the input value.</value> 103 public bool invert; 104 105 /// <summary> 106 /// If true, normalize the input value to [0..1] or [-1..1] (depending on the 107 /// value of <see cref="normalizeZero"/>. Off by default. 108 /// </summary> 109 /// <value>Whether to normalize input values or not.</value> 110 /// <seealso cref="normalizeMin"/> 111 /// <seealso cref="normalizeMax"/> 112 public bool normalize; 113 114 ////REVIEW: shouldn't these come from the control min/max value by default? 115 116 /// <summary> 117 /// If <see cref="normalize"/> is on, this is the input value that corresponds 118 /// to 0 of the normalized [0..1] or [-1..1] range. 119 /// </summary> 120 /// <value>Input value that should become 0 or -1.</value> 121 /// <remarks> 122 /// In other words, with <see cref="normalize"/> on, input values are mapped from 123 /// the range of [normalizeMin..normalizeMax] to [0..1] or [-1..1] (depending on 124 /// <see cref="normalizeZero"/>). 125 /// </remarks> 126 public float normalizeMin; 127 128 /// <summary> 129 /// If <see cref="normalize"/> is on, this is the input value that corresponds 130 /// to 1 of the normalized [0..1] or [-1..1] range. 131 /// </summary> 132 /// <value>Input value that should become 1.</value> 133 /// <remarks> 134 /// In other words, with <see cref="normalize"/> on, input values are mapped from 135 /// the range of [normalizeMin..normalizeMax] to [0..1] or [-1..1] (depending on 136 /// <see cref="normalizeZero"/>). 137 /// </remarks> 138 public float normalizeMax; 139 140 /// <summary> 141 /// Where to put the zero point of the normalization range. Only relevant 142 /// if <see cref="normalize"/> is set to true. Defaults to 0. 143 /// </summary> 144 /// <value>Zero point of normalization range.</value> 145 /// <remarks> 146 /// The value of this property determines where the zero point is located in the 147 /// range established by <see cref="normalizeMin"/> and <see cref="normalizeMax"/>. 148 /// 149 /// If <c>normalizeZero</c> is placed at <see cref="normalizeMin"/>, the normalization 150 /// returns a value in the [0..1] range mapped from the input value range of 151 /// <see cref="normalizeMin"/> and <see cref="normalizeMax"/>. 152 /// 153 /// If <c>normalizeZero</c> is placed in-between <see cref="normalizeMin"/> and 154 /// <see cref="normalizeMax"/>, normalization returns a value in the [-1..1] mapped 155 /// from the input value range of <see cref="normalizeMin"/> and <see cref="normalizeMax"/> 156 /// and the zero point between the two established by <c>normalizeZero</c>. 157 /// </remarks> 158 public float normalizeZero; 159 160 ////REVIEW: why not just have a default scaleFactor of 1? 161 162 /// <summary> 163 /// Whether the scale the input value by <see cref="scaleFactor"/>. Off by default. 164 /// </summary> 165 /// <value>True if inputs should be scaled by <see cref="scaleFactor"/>.</value> 166 public bool scale; 167 168 /// <summary> 169 /// Value to multiple input values with. Only applied if <see cref="scale"/> is <c>true</c>. 170 /// </summary> 171 /// <value>Multiplier for input values.</value> 172 public float scaleFactor; 173 174 /// <summary> 175 /// Apply modifications to the given value according to the parameters configured 176 /// on the control (<see cref="clamp"/>, <see cref="normalize"/>, etc). 177 /// </summary> 178 /// <param name="value">Input value.</param> 179 /// <returns>A processed value (clamped, normalized, etc).</returns> 180 /// <seealso cref="clamp"/> 181 /// <seealso cref="normalize"/> 182 /// <seealso cref="scale"/> 183 /// <seealso cref="invert"/> 184 [MethodImpl(MethodImplOptions.AggressiveInlining)] 185 protected float Preprocess(float value) 186 { 187 if (scale) 188 value *= scaleFactor; 189 if (clamp == Clamp.ToConstantBeforeNormalize) 190 { 191 if (value < clampMin || value > clampMax) 192 value = clampConstant; 193 } 194 else if (clamp == Clamp.BeforeNormalize) 195 value = Mathf.Clamp(value, clampMin, clampMax); 196 if (normalize) 197 value = NormalizeProcessor.Normalize(value, normalizeMin, normalizeMax, normalizeZero); 198 if (clamp == Clamp.AfterNormalize) 199 value = Mathf.Clamp(value, clampMin, clampMax); 200 if (invert) 201 value *= -1.0f; 202 return value; 203 } 204 205 private float Unpreprocess(float value) 206 { 207 // Does not reverse the effect of clamping (we don't know what the unclamped value should be). 208 209 if (invert) 210 value *= -1f; 211 if (normalize) 212 value = NormalizeProcessor.Denormalize(value, normalizeMin, normalizeMax, normalizeZero); 213 if (scale) 214 value /= scaleFactor; 215 return value; 216 } 217 218 /// <summary> 219 /// Default-initialize the control. 220 /// </summary> 221 /// <remarks> 222 /// Defaults the format to <see cref="InputStateBlock.FormatFloat"/>. 223 /// </remarks> 224 public AxisControl() 225 { 226 m_StateBlock.format = InputStateBlock.FormatFloat; 227 } 228 229 protected override void FinishSetup() 230 { 231 base.FinishSetup(); 232 233 // if we don't have any default state, and we are using normalizeZero, then the default value 234 // should not be zero. Generate it from normalizeZero. 235 if (!hasDefaultState && normalize && Mathf.Abs(normalizeZero) > Mathf.Epsilon) 236 m_DefaultState = stateBlock.FloatToPrimitiveValue(normalizeZero); 237 } 238 239 /// <inheritdoc /> 240 public override unsafe float ReadUnprocessedValueFromState(void* statePtr) 241 { 242 switch (m_OptimizedControlDataType) 243 { 244 case InputStateBlock.kFormatFloat: 245 return *(float*)((byte*)statePtr + m_StateBlock.m_ByteOffset); 246 case InputStateBlock.kFormatByte: 247 return *((byte*)statePtr + m_StateBlock.m_ByteOffset) != 0 ? 1.0f : 0.0f; 248 default: 249 { 250 var value = stateBlock.ReadFloat(statePtr); 251 ////REVIEW: this isn't very raw 252 return Preprocess(value); 253 } 254 } 255 } 256 257 /// <inheritdoc /> 258 public override unsafe void WriteValueIntoState(float value, void* statePtr) 259 { 260 switch (m_OptimizedControlDataType) 261 { 262 case InputStateBlock.kFormatFloat: 263 *(float*)((byte*)statePtr + m_StateBlock.m_ByteOffset) = value; 264 break; 265 case InputStateBlock.kFormatByte: 266 *((byte*)statePtr + m_StateBlock.m_ByteOffset) = (byte)(value >= 0.5f ? 1 : 0); 267 break; 268 default: 269 value = Unpreprocess(value); 270 stateBlock.WriteFloat(statePtr, value); 271 break; 272 } 273 } 274 275 /// <inheritdoc /> 276 public override unsafe bool CompareValue(void* firstStatePtr, void* secondStatePtr) 277 { 278 var currentValue = ReadValueFromState(firstStatePtr); 279 var valueInState = ReadValueFromState(secondStatePtr); 280 return !Mathf.Approximately(currentValue, valueInState); 281 } 282 283 /// <inheritdoc /> 284 public override unsafe float EvaluateMagnitude(void* statePtr) 285 { 286 return EvaluateMagnitude(ReadValueFromStateWithCaching(statePtr)); 287 } 288 289 private float EvaluateMagnitude(float value) 290 { 291 if (m_MinValue.isEmpty || m_MaxValue.isEmpty) 292 return Mathf.Abs(value); 293 294 var min = m_MinValue.ToSingle(); 295 var max = m_MaxValue.ToSingle(); 296 297 var clampedValue = Mathf.Clamp(value, min, max); 298 299 // If part of our range is in negative space, evaluate magnitude as two 300 // separate subspaces. 301 if (min < 0) 302 { 303 if (clampedValue < 0) 304 return NormalizeProcessor.Normalize(Mathf.Abs(clampedValue), 0, Mathf.Abs(min), 0); 305 return NormalizeProcessor.Normalize(clampedValue, 0, max, 0); 306 } 307 308 return NormalizeProcessor.Normalize(clampedValue, min, max, 0); 309 } 310 311 protected override FourCC CalculateOptimizedControlDataType() 312 { 313 var noProcessingNeeded = 314 clamp == Clamp.None && 315 invert == false && 316 normalize == false && 317 scale == false; 318 319 if (noProcessingNeeded && 320 m_StateBlock.format == InputStateBlock.FormatFloat && 321 m_StateBlock.sizeInBits == 32 && 322 m_StateBlock.bitOffset == 0) 323 return InputStateBlock.FormatFloat; 324 if (noProcessingNeeded && 325 m_StateBlock.format == InputStateBlock.FormatBit && 326 // 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 327 // like in the mouse where LMB/RMB map to the same byte, just LMB maps to first bit and RMB maps to second bit 328 m_StateBlock.sizeInBits == 8 && 329 m_StateBlock.bitOffset == 0) 330 return InputStateBlock.FormatByte; 331 return InputStateBlock.FormatInvalid; 332 } 333 } 334}