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}