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;
        }
    }
}