Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/gamepadmotionhelpers/GamepadMotion.hpp
21425 views
1
// Copyright (c) 2020-2023 Julian "Jibb" Smart
2
// Released under the MIT license. See https://github.com/JibbSmart/GamepadMotionHelpers/blob/main/LICENSE for more info
3
// Version 9
4
5
#pragma once
6
7
#define _USE_MATH_DEFINES
8
#include <math.h>
9
#include <algorithm> // std::min, std::max and std::clamp
10
11
// You don't need to look at these. These will just be used internally by the GamepadMotion class declared below.
12
// You can ignore anything in namespace GamepadMotionHelpers.
13
class GamepadMotionSettings;
14
class GamepadMotion;
15
16
namespace GamepadMotionHelpers
17
{
18
struct GyroCalibration
19
{
20
float X;
21
float Y;
22
float Z;
23
float AccelMagnitude;
24
int NumSamples;
25
};
26
27
struct Quat
28
{
29
float w;
30
float x;
31
float y;
32
float z;
33
34
Quat();
35
Quat(float inW, float inX, float inY, float inZ);
36
void Set(float inW, float inX, float inY, float inZ);
37
Quat& operator*=(const Quat& rhs);
38
friend Quat operator*(Quat lhs, const Quat& rhs);
39
void Normalize();
40
Quat Normalized() const;
41
void Invert();
42
Quat Inverse() const;
43
};
44
45
struct Vec
46
{
47
float x;
48
float y;
49
float z;
50
51
Vec();
52
Vec(float inValue);
53
Vec(float inX, float inY, float inZ);
54
void Set(float inX, float inY, float inZ);
55
float Length() const;
56
float LengthSquared() const;
57
void Normalize();
58
Vec Normalized() const;
59
float Dot(const Vec& other) const;
60
Vec Cross(const Vec& other) const;
61
Vec Min(const Vec& other) const;
62
Vec Max(const Vec& other) const;
63
Vec Abs() const;
64
Vec Lerp(const Vec& other, float factor) const;
65
Vec Lerp(const Vec& other, const Vec& factor) const;
66
Vec& operator+=(const Vec& rhs);
67
friend Vec operator+(Vec lhs, const Vec& rhs);
68
Vec& operator-=(const Vec& rhs);
69
friend Vec operator-(Vec lhs, const Vec& rhs);
70
Vec& operator*=(const float rhs);
71
friend Vec operator*(Vec lhs, const float rhs);
72
Vec& operator/=(const float rhs);
73
friend Vec operator/(Vec lhs, const float rhs);
74
Vec& operator*=(const Quat& rhs);
75
friend Vec operator*(Vec lhs, const Quat& rhs);
76
Vec operator-() const;
77
};
78
79
struct SensorMinMaxWindow
80
{
81
Vec MinGyro;
82
Vec MaxGyro;
83
Vec MeanGyro;
84
Vec MinAccel;
85
Vec MaxAccel;
86
Vec MeanAccel;
87
Vec StartAccel;
88
int NumSamples = 0;
89
float TimeSampled = 0.f;
90
91
SensorMinMaxWindow();
92
void Reset(float remainder);
93
void AddSample(const Vec& inGyro, const Vec& inAccel, float deltaTime);
94
Vec GetMidGyro();
95
};
96
97
struct AutoCalibration
98
{
99
SensorMinMaxWindow MinMaxWindow;
100
Vec SmoothedAngularVelocityGyro;
101
Vec SmoothedAngularVelocityAccel;
102
Vec SmoothedPreviousAccel;
103
Vec PreviousAccel;
104
105
AutoCalibration();
106
void Reset();
107
bool AddSampleStillness(const Vec& inGyro, const Vec& inAccel, float deltaTime, bool doSensorFusion);
108
void NoSampleStillness();
109
bool AddSampleSensorFusion(const Vec& inGyro, const Vec& inAccel, float deltaTime);
110
void NoSampleSensorFusion();
111
void SetCalibrationData(GyroCalibration* calibrationData);
112
void SetSettings(GamepadMotionSettings* settings);
113
114
float Confidence = 0.f;
115
bool IsSteady() { return bIsSteady; }
116
117
private:
118
Vec MinDeltaGyro = Vec(1.f);
119
Vec MinDeltaAccel = Vec(0.25f);
120
float RecalibrateThreshold = 1.f;
121
float SensorFusionSkippedTime = 0.f;
122
float TimeSteadySensorFusion = 0.f;
123
float TimeSteadyStillness = 0.f;
124
bool bIsSteady = false;
125
126
GyroCalibration* CalibrationData;
127
GamepadMotionSettings* Settings;
128
};
129
130
struct Motion
131
{
132
Quat Quaternion;
133
Vec Accel;
134
Vec Grav;
135
136
Vec SmoothAccel = Vec();
137
float Shakiness = 0.f;
138
const float ShortSteadinessHalfTime = 0.25f;
139
const float LongSteadinessHalfTime = 1.f;
140
141
Motion();
142
void Reset();
143
void Update(float inGyroX, float inGyroY, float inGyroZ, float inAccelX, float inAccelY, float inAccelZ, float gravityLength, float deltaTime);
144
void SetSettings(GamepadMotionSettings* settings);
145
146
private:
147
GamepadMotionSettings* Settings;
148
};
149
150
enum CalibrationMode
151
{
152
Manual = 0,
153
Stillness = 1,
154
SensorFusion = 2,
155
};
156
157
// https://stackoverflow.com/a/1448478/1130520
158
inline CalibrationMode operator|(CalibrationMode a, CalibrationMode b)
159
{
160
return static_cast<CalibrationMode>(static_cast<int>(a) | static_cast<int>(b));
161
}
162
163
inline CalibrationMode operator&(CalibrationMode a, CalibrationMode b)
164
{
165
return static_cast<CalibrationMode>(static_cast<int>(a) & static_cast<int>(b));
166
}
167
168
inline CalibrationMode operator~(CalibrationMode a)
169
{
170
return static_cast<CalibrationMode>(~static_cast<int>(a));
171
}
172
173
// https://stackoverflow.com/a/23152590/1130520
174
inline CalibrationMode& operator|=(CalibrationMode& a, CalibrationMode b)
175
{
176
return (CalibrationMode&)((int&)(a) |= static_cast<int>(b));
177
}
178
179
inline CalibrationMode& operator&=(CalibrationMode& a, CalibrationMode b)
180
{
181
return (CalibrationMode&)((int&)(a) &= static_cast<int>(b));
182
}
183
}
184
185
// Note that I'm using a Y-up coordinate system. This is to follow the convention set by the motion sensors in
186
// PlayStation controllers, which was what I was using when writing in this. But for the record, Z-up is
187
// better for most games (XY ground-plane in 3D games simplifies using 2D vectors in navigation, for example).
188
189
// Gyro units should be degrees per second. Accelerometer should be g-force (approx. 9.8 m/s^2 = 1 g). If you're using
190
// radians per second, meters per second squared, etc, conversion should be simple.
191
192
class GamepadMotionSettings
193
{
194
public:
195
int MinStillnessSamples = 10;
196
float MinStillnessCollectionTime = 0.5f;
197
float MinStillnessCorrectionTime = 2.f;
198
float MaxStillnessError = 2.f;
199
float StillnessSampleDeteriorationRate = 0.2f;
200
float StillnessErrorClimbRate = 0.1f;
201
float StillnessErrorDropOnRecalibrate = 0.1f;
202
float StillnessCalibrationEaseInTime = 3.f;
203
float StillnessCalibrationHalfTime = 0.1f;
204
float StillnessConfidenceRate = 1.f;
205
206
float StillnessGyroDelta = -1.f;
207
float StillnessAccelDelta = -1.f;
208
209
float SensorFusionCalibrationSmoothingStrength = 2.f;
210
float SensorFusionAngularAccelerationThreshold = 20.f;
211
float SensorFusionCalibrationEaseInTime = 3.f;
212
float SensorFusionCalibrationHalfTime = 0.1f;
213
float SensorFusionConfidenceRate = 1.f;
214
215
float GravityCorrectionShakinessMaxThreshold = 0.4f;
216
float GravityCorrectionShakinessMinThreshold = 0.01f;
217
218
float GravityCorrectionStillSpeed = 1.f;
219
float GravityCorrectionShakySpeed = 0.1f;
220
221
float GravityCorrectionGyroFactor = 0.1f;
222
float GravityCorrectionGyroMinThreshold = 0.05f;
223
float GravityCorrectionGyroMaxThreshold = 0.25f;
224
225
float GravityCorrectionMinimumSpeed = 0.01f;
226
};
227
228
class GamepadMotion
229
{
230
public:
231
GamepadMotion();
232
233
void Reset();
234
235
void ProcessMotion(float gyroX, float gyroY, float gyroZ,
236
float accelX, float accelY, float accelZ, float deltaTime);
237
238
// reading the current state
239
void GetCalibratedGyro(float& x, float& y, float& z);
240
void GetGravity(float& x, float& y, float& z);
241
void GetProcessedAcceleration(float& x, float& y, float& z);
242
void GetOrientation(float& w, float& x, float& y, float& z);
243
void GetPlayerSpaceGyro(float& x, float& y, const float yawRelaxFactor = 1.41f);
244
static void CalculatePlayerSpaceGyro(float& x, float& y, const float gyroX, const float gyroY, const float gyroZ, const float gravX, const float gravY, const float gravZ, const float yawRelaxFactor = 1.41f);
245
void GetWorldSpaceGyro(float& x, float& y, const float sideReductionThreshold = 0.125f);
246
static void CalculateWorldSpaceGyro(float& x, float& y, const float gyroX, const float gyroY, const float gyroZ, const float gravX, const float gravY, const float gravZ, const float sideReductionThreshold = 0.125f);
247
248
// gyro calibration functions
249
void StartContinuousCalibration();
250
void PauseContinuousCalibration();
251
void ResetContinuousCalibration();
252
void GetCalibrationOffset(float& xOffset, float& yOffset, float& zOffset);
253
void SetCalibrationOffset(float xOffset, float yOffset, float zOffset, int weight);
254
float GetAutoCalibrationConfidence();
255
void SetAutoCalibrationConfidence(float newConfidence);
256
bool GetAutoCalibrationIsSteady();
257
258
GamepadMotionHelpers::CalibrationMode GetCalibrationMode();
259
void SetCalibrationMode(GamepadMotionHelpers::CalibrationMode calibrationMode);
260
261
void ResetMotion();
262
263
GamepadMotionSettings Settings;
264
265
private:
266
GamepadMotionHelpers::Vec Gyro;
267
GamepadMotionHelpers::Vec RawAccel;
268
GamepadMotionHelpers::Motion Motion;
269
GamepadMotionHelpers::GyroCalibration GyroCalibration;
270
GamepadMotionHelpers::AutoCalibration AutoCalibration;
271
GamepadMotionHelpers::CalibrationMode CurrentCalibrationMode;
272
273
bool IsCalibrating;
274
void PushSensorSamples(float gyroX, float gyroY, float gyroZ, float accelMagnitude);
275
void GetCalibratedSensor(float& gyroOffsetX, float& gyroOffsetY, float& gyroOffsetZ, float& accelMagnitude);
276
};
277
278
///////////// Everything below here are just implementation details /////////////
279
280
namespace GamepadMotionHelpers
281
{
282
inline Quat::Quat()
283
{
284
w = 1.0f;
285
x = 0.0f;
286
y = 0.0f;
287
z = 0.0f;
288
}
289
290
inline Quat::Quat(float inW, float inX, float inY, float inZ)
291
{
292
w = inW;
293
x = inX;
294
y = inY;
295
z = inZ;
296
}
297
298
inline static Quat AngleAxis(float inAngle, float inX, float inY, float inZ)
299
{
300
const float sinHalfAngle = sinf(inAngle * 0.5f);
301
Vec inAxis = Vec(inX, inY, inZ);
302
inAxis.Normalize();
303
inAxis *= sinHalfAngle;
304
Quat result = Quat(cosf(inAngle * 0.5f), inAxis.x, inAxis.y, inAxis.z);
305
return result;
306
}
307
308
inline void Quat::Set(float inW, float inX, float inY, float inZ)
309
{
310
w = inW;
311
x = inX;
312
y = inY;
313
z = inZ;
314
}
315
316
inline Quat& Quat::operator*=(const Quat& rhs)
317
{
318
Set(w * rhs.w - x * rhs.x - y * rhs.y - z * rhs.z,
319
w * rhs.x + x * rhs.w + y * rhs.z - z * rhs.y,
320
w * rhs.y - x * rhs.z + y * rhs.w + z * rhs.x,
321
w * rhs.z + x * rhs.y - y * rhs.x + z * rhs.w);
322
return *this;
323
}
324
325
inline Quat operator*(Quat lhs, const Quat& rhs)
326
{
327
lhs *= rhs;
328
return lhs;
329
}
330
331
inline void Quat::Normalize()
332
{
333
const float length = sqrtf(w * w + x * x + y * y + z * z);
334
const float fixFactor = 1.0f / length;
335
336
w *= fixFactor;
337
x *= fixFactor;
338
y *= fixFactor;
339
z *= fixFactor;
340
341
return;
342
}
343
344
inline Quat Quat::Normalized() const
345
{
346
Quat result = *this;
347
result.Normalize();
348
return result;
349
}
350
351
inline void Quat::Invert()
352
{
353
x = -x;
354
y = -y;
355
z = -z;
356
return;
357
}
358
359
inline Quat Quat::Inverse() const
360
{
361
Quat result = *this;
362
result.Invert();
363
return result;
364
}
365
366
inline Vec::Vec()
367
{
368
x = 0.0f;
369
y = 0.0f;
370
z = 0.0f;
371
}
372
373
inline Vec::Vec(float inValue)
374
{
375
x = inValue;
376
y = inValue;
377
z = inValue;
378
}
379
380
inline Vec::Vec(float inX, float inY, float inZ)
381
{
382
x = inX;
383
y = inY;
384
z = inZ;
385
}
386
387
inline void Vec::Set(float inX, float inY, float inZ)
388
{
389
x = inX;
390
y = inY;
391
z = inZ;
392
}
393
394
inline float Vec::Length() const
395
{
396
return sqrtf(x * x + y * y + z * z);
397
}
398
399
inline float Vec::LengthSquared() const
400
{
401
return x * x + y * y + z * z;
402
}
403
404
inline void Vec::Normalize()
405
{
406
const float length = Length();
407
if (length == 0.0)
408
{
409
return;
410
}
411
const float fixFactor = 1.0f / length;
412
413
x *= fixFactor;
414
y *= fixFactor;
415
z *= fixFactor;
416
return;
417
}
418
419
inline Vec Vec::Normalized() const
420
{
421
Vec result = *this;
422
result.Normalize();
423
return result;
424
}
425
426
inline Vec& Vec::operator+=(const Vec& rhs)
427
{
428
Set(x + rhs.x, y + rhs.y, z + rhs.z);
429
return *this;
430
}
431
432
inline Vec operator+(Vec lhs, const Vec& rhs)
433
{
434
lhs += rhs;
435
return lhs;
436
}
437
438
inline Vec& Vec::operator-=(const Vec& rhs)
439
{
440
Set(x - rhs.x, y - rhs.y, z - rhs.z);
441
return *this;
442
}
443
444
inline Vec operator-(Vec lhs, const Vec& rhs)
445
{
446
lhs -= rhs;
447
return lhs;
448
}
449
450
inline Vec& Vec::operator*=(const float rhs)
451
{
452
Set(x * rhs, y * rhs, z * rhs);
453
return *this;
454
}
455
456
inline Vec operator*(Vec lhs, const float rhs)
457
{
458
lhs *= rhs;
459
return lhs;
460
}
461
462
inline Vec& Vec::operator/=(const float rhs)
463
{
464
Set(x / rhs, y / rhs, z / rhs);
465
return *this;
466
}
467
468
inline Vec operator/(Vec lhs, const float rhs)
469
{
470
lhs /= rhs;
471
return lhs;
472
}
473
474
inline Vec& Vec::operator*=(const Quat& rhs)
475
{
476
Quat temp = rhs * Quat(0.0f, x, y, z) * rhs.Inverse();
477
Set(temp.x, temp.y, temp.z);
478
return *this;
479
}
480
481
inline Vec operator*(Vec lhs, const Quat& rhs)
482
{
483
lhs *= rhs;
484
return lhs;
485
}
486
487
inline Vec Vec::operator-() const
488
{
489
Vec result = Vec(-x, -y, -z);
490
return result;
491
}
492
493
inline float Vec::Dot(const Vec& other) const
494
{
495
return x * other.x + y * other.y + z * other.z;
496
}
497
498
inline Vec Vec::Cross(const Vec& other) const
499
{
500
return Vec(y * other.z - z * other.y,
501
z * other.x - x * other.z,
502
x * other.y - y * other.x);
503
}
504
505
inline Vec Vec::Min(const Vec& other) const
506
{
507
return Vec(x < other.x ? x : other.x,
508
y < other.y ? y : other.y,
509
z < other.z ? z : other.z);
510
}
511
512
inline Vec Vec::Max(const Vec& other) const
513
{
514
return Vec(x > other.x ? x : other.x,
515
y > other.y ? y : other.y,
516
z > other.z ? z : other.z);
517
}
518
519
inline Vec Vec::Abs() const
520
{
521
return Vec(x > 0 ? x : -x,
522
y > 0 ? y : -y,
523
z > 0 ? z : -z);
524
}
525
526
inline Vec Vec::Lerp(const Vec& other, float factor) const
527
{
528
return *this + (other - *this) * factor;
529
}
530
531
inline Vec Vec::Lerp(const Vec& other, const Vec& factor) const
532
{
533
return Vec(this->x + (other.x - this->x) * factor.x,
534
this->y + (other.y - this->y) * factor.y,
535
this->z + (other.z - this->z) * factor.z);
536
}
537
538
inline Motion::Motion()
539
{
540
Reset();
541
}
542
543
inline void Motion::Reset()
544
{
545
Quaternion.Set(1.f, 0.f, 0.f, 0.f);
546
Accel.Set(0.f, 0.f, 0.f);
547
Grav.Set(0.f, 0.f, 0.f);
548
SmoothAccel.Set(0.f, 0.f, 0.f);
549
Shakiness = 0.f;
550
}
551
552
/// <summary>
553
/// The gyro inputs should be calibrated degrees per second but have no other processing. Acceleration is in G units (1 = approx. 9.8m/s^2)
554
/// </summary>
555
inline void Motion::Update(float inGyroX, float inGyroY, float inGyroZ, float inAccelX, float inAccelY, float inAccelZ, float gravityLength, float deltaTime)
556
{
557
if (!Settings)
558
{
559
return;
560
}
561
562
// get settings
563
const float gravityCorrectionShakinessMinThreshold = Settings->GravityCorrectionShakinessMinThreshold;
564
const float gravityCorrectionShakinessMaxThreshold = Settings->GravityCorrectionShakinessMaxThreshold;
565
const float gravityCorrectionStillSpeed = Settings->GravityCorrectionStillSpeed;
566
const float gravityCorrectionShakySpeed = Settings->GravityCorrectionShakySpeed;
567
const float gravityCorrectionGyroFactor = Settings->GravityCorrectionGyroFactor;
568
const float gravityCorrectionGyroMinThreshold = Settings->GravityCorrectionGyroMinThreshold;
569
const float gravityCorrectionGyroMaxThreshold = Settings->GravityCorrectionGyroMaxThreshold;
570
const float gravityCorrectionMinimumSpeed = Settings->GravityCorrectionMinimumSpeed;
571
572
const Vec axis = Vec(inGyroX, inGyroY, inGyroZ);
573
const Vec accel = Vec(inAccelX, inAccelY, inAccelZ);
574
const float angleSpeed = axis.Length() * (float)M_PI / 180.0f;
575
const float angle = angleSpeed * deltaTime;
576
577
// rotate
578
Quat rotation = AngleAxis(angle, axis.x, axis.y, axis.z);
579
Quaternion *= rotation; // do it this way because it's a local rotation, not global
580
581
//printf("Quat: %.4f %.4f %.4f %.4f\n",
582
// Quaternion.w, Quaternion.x, Quaternion.y, Quaternion.z);
583
float accelMagnitude = accel.Length();
584
if (accelMagnitude > 0.0f)
585
{
586
const Vec accelNorm = accel / accelMagnitude;
587
// account for rotation when tracking smoothed acceleration
588
SmoothAccel *= rotation.Inverse();
589
//printf("Absolute Accel: %.4f %.4f %.4f\n",
590
// absoluteAccel.x, absoluteAccel.y, absoluteAccel.z);
591
const float smoothFactor = ShortSteadinessHalfTime <= 0.f ? 0.f : exp2f(-deltaTime / ShortSteadinessHalfTime);
592
Shakiness *= smoothFactor;
593
Shakiness = std::max(Shakiness, (accel - SmoothAccel).Length());
594
SmoothAccel = accel.Lerp(SmoothAccel, smoothFactor);
595
596
//printf("Shakiness: %.4f\n", Shakiness);
597
598
// update grav by rotation
599
Grav *= rotation.Inverse();
600
// we want to close the gap between grav and raw acceleration. What's the difference
601
const Vec gravToAccel = (accelNorm * -gravityLength) - Grav;
602
const Vec gravToAccelDir = gravToAccel.Normalized();
603
// adjustment rate
604
float gravCorrectionSpeed;
605
if (gravityCorrectionShakinessMinThreshold < gravityCorrectionShakinessMaxThreshold)
606
{
607
gravCorrectionSpeed = gravityCorrectionStillSpeed + (gravityCorrectionShakySpeed - gravityCorrectionStillSpeed) * std::clamp((Shakiness - gravityCorrectionShakinessMinThreshold) / (gravityCorrectionShakinessMaxThreshold - gravityCorrectionShakinessMinThreshold), 0.f, 1.f);
608
}
609
else
610
{
611
gravCorrectionSpeed = Shakiness < gravityCorrectionShakinessMaxThreshold ? gravityCorrectionStillSpeed : gravityCorrectionShakySpeed;
612
}
613
// we also limit it to be no faster than a given proportion of the gyro rate, or the minimum gravity correction speed
614
const float gyroGravCorrectionLimit = std::max(angleSpeed * gravityCorrectionGyroFactor, gravityCorrectionMinimumSpeed);
615
if (gravCorrectionSpeed > gyroGravCorrectionLimit)
616
{
617
float closeEnoughFactor;
618
if (gravityCorrectionGyroMinThreshold < gravityCorrectionGyroMaxThreshold)
619
{
620
closeEnoughFactor = std::clamp((gravToAccel.Length() - gravityCorrectionGyroMinThreshold) / (gravityCorrectionGyroMaxThreshold - gravityCorrectionGyroMinThreshold), 0.f, 1.f);
621
}
622
else
623
{
624
closeEnoughFactor = gravToAccel.Length() < gravityCorrectionGyroMaxThreshold ? 0.f : 1.f;
625
}
626
gravCorrectionSpeed = gyroGravCorrectionLimit + (gravCorrectionSpeed - gyroGravCorrectionLimit) * closeEnoughFactor;
627
}
628
const Vec gravToAccelDelta = gravToAccelDir * gravCorrectionSpeed * deltaTime;
629
if (gravToAccelDelta.LengthSquared() < gravToAccel.LengthSquared())
630
{
631
Grav += gravToAccelDelta;
632
}
633
else
634
{
635
Grav = accelNorm * -gravityLength;
636
}
637
638
const Vec gravityDirection = Grav.Normalized() * Quaternion.Inverse(); // absolute gravity direction
639
const float errorAngle = acosf(std::clamp(Vec(0.0f, -1.0f, 0.0f).Dot(gravityDirection), -1.f, 1.f));
640
const Vec flattened = Vec(0.0f, -1.0f, 0.0f).Cross(gravityDirection);
641
Quat correctionQuat = AngleAxis(errorAngle, flattened.x, flattened.y, flattened.z);
642
Quaternion = Quaternion * correctionQuat;
643
644
Accel = accel + Grav;
645
}
646
else
647
{
648
Grav *= rotation.Inverse();
649
Accel = Grav;
650
}
651
Quaternion.Normalize();
652
}
653
654
inline void Motion::SetSettings(GamepadMotionSettings* settings)
655
{
656
Settings = settings;
657
}
658
659
inline SensorMinMaxWindow::SensorMinMaxWindow()
660
{
661
Reset(0.f);
662
}
663
664
inline void SensorMinMaxWindow::Reset(float remainder)
665
{
666
NumSamples = 0;
667
TimeSampled = remainder;
668
}
669
670
inline void SensorMinMaxWindow::AddSample(const Vec& inGyro, const Vec& inAccel, float deltaTime)
671
{
672
if (NumSamples == 0)
673
{
674
MaxGyro = inGyro;
675
MinGyro = inGyro;
676
MeanGyro = inGyro;
677
MaxAccel = inAccel;
678
MinAccel = inAccel;
679
MeanAccel = inAccel;
680
StartAccel = inAccel;
681
NumSamples = 1;
682
TimeSampled += deltaTime;
683
return;
684
}
685
686
MaxGyro = MaxGyro.Max(inGyro);
687
MinGyro = MinGyro.Min(inGyro);
688
MaxAccel = MaxAccel.Max(inAccel);
689
MinAccel = MinAccel.Min(inAccel);
690
691
NumSamples++;
692
TimeSampled += deltaTime;
693
694
Vec delta = inGyro - MeanGyro;
695
MeanGyro += delta * (1.f / NumSamples);
696
delta = inAccel - MeanAccel;
697
MeanAccel += delta * (1.f / NumSamples);
698
}
699
700
inline Vec SensorMinMaxWindow::GetMidGyro()
701
{
702
return MeanGyro;
703
}
704
705
inline AutoCalibration::AutoCalibration()
706
{
707
CalibrationData = nullptr;
708
Reset();
709
}
710
711
inline void AutoCalibration::Reset()
712
{
713
MinMaxWindow.Reset(0.f);
714
Confidence = 0.f;
715
bIsSteady = false;
716
MinDeltaGyro = Vec(1.f);
717
MinDeltaAccel = Vec(0.25f);
718
RecalibrateThreshold = 1.f;
719
SensorFusionSkippedTime = 0.f;
720
TimeSteadySensorFusion = 0.f;
721
TimeSteadyStillness = 0.f;
722
}
723
724
inline bool AutoCalibration::AddSampleStillness(const Vec& inGyro, const Vec& inAccel, float deltaTime, bool doSensorFusion)
725
{
726
if (inGyro.x == 0.f && inGyro.y == 0.f && inGyro.z == 0.f &&
727
inAccel.x == 0.f && inAccel.y == 0.f && inAccel.z == 0.f)
728
{
729
// zeroes are almost certainly not valid inputs
730
return false;
731
}
732
733
if (!Settings)
734
{
735
return false;
736
}
737
738
if (!CalibrationData)
739
{
740
return false;
741
}
742
743
// get settings
744
const int minStillnessSamples = Settings->MinStillnessSamples;
745
const float minStillnessCollectionTime = Settings->MinStillnessCollectionTime;
746
const float minStillnessCorrectionTime = Settings->MinStillnessCorrectionTime;
747
const float maxStillnessError = Settings->MaxStillnessError;
748
const float stillnessSampleDeteriorationRate = Settings->StillnessSampleDeteriorationRate;
749
const float stillnessErrorClimbRate = Settings->StillnessErrorClimbRate;
750
const float stillnessErrorDropOnRecalibrate = Settings->StillnessErrorDropOnRecalibrate;
751
const float stillnessCalibrationEaseInTime = Settings->StillnessCalibrationEaseInTime;
752
const float stillnessCalibrationHalfTime = Settings->StillnessCalibrationHalfTime * Confidence;
753
const float stillnessConfidenceRate = Settings->StillnessConfidenceRate;
754
const float stillnessGyroDelta = Settings->StillnessGyroDelta;
755
const float stillnessAccelDelta = Settings->StillnessAccelDelta;
756
757
MinMaxWindow.AddSample(inGyro, inAccel, deltaTime);
758
// get deltas
759
const Vec gyroDelta = MinMaxWindow.MaxGyro - MinMaxWindow.MinGyro;
760
const Vec accelDelta = MinMaxWindow.MaxAccel - MinMaxWindow.MinAccel;
761
762
bool calibrated = false;
763
bool isSteady = false;
764
const Vec climbThisTick = Vec(stillnessSampleDeteriorationRate * deltaTime);
765
if (stillnessGyroDelta < 0.f)
766
{
767
if (Confidence < 1.f)
768
{
769
MinDeltaGyro += climbThisTick;
770
}
771
}
772
else
773
{
774
MinDeltaGyro = Vec(stillnessGyroDelta);
775
}
776
if (stillnessAccelDelta < 0.f)
777
{
778
if (Confidence < 1.f)
779
{
780
MinDeltaAccel += climbThisTick;
781
}
782
}
783
else
784
{
785
MinDeltaAccel = Vec(stillnessAccelDelta);
786
}
787
788
//printf("Deltas: %.4f %.4f %.4f; %.4f %.4f %.4f\n",
789
// gyroDelta.x, gyroDelta.y, gyroDelta.z,
790
// accelDelta.x, accelDelta.y, accelDelta.z);
791
792
if (MinMaxWindow.NumSamples >= minStillnessSamples && MinMaxWindow.TimeSampled >= minStillnessCollectionTime)
793
{
794
MinDeltaGyro = MinDeltaGyro.Min(gyroDelta);
795
MinDeltaAccel = MinDeltaAccel.Min(accelDelta);
796
}
797
else
798
{
799
RecalibrateThreshold = std::min(RecalibrateThreshold + stillnessErrorClimbRate * deltaTime, maxStillnessError);
800
return false;
801
}
802
803
// check that all inputs are below appropriate thresholds to be considered "still"
804
if (gyroDelta.x <= MinDeltaGyro.x * RecalibrateThreshold &&
805
gyroDelta.y <= MinDeltaGyro.y * RecalibrateThreshold &&
806
gyroDelta.z <= MinDeltaGyro.z * RecalibrateThreshold &&
807
accelDelta.x <= MinDeltaAccel.x * RecalibrateThreshold &&
808
accelDelta.y <= MinDeltaAccel.y * RecalibrateThreshold &&
809
accelDelta.z <= MinDeltaAccel.z * RecalibrateThreshold)
810
{
811
if (MinMaxWindow.NumSamples >= minStillnessSamples && MinMaxWindow.TimeSampled >= minStillnessCorrectionTime)
812
{
813
TimeSteadyStillness = std::min(TimeSteadyStillness + deltaTime, stillnessCalibrationEaseInTime);
814
const float calibrationEaseIn = stillnessCalibrationEaseInTime <= 0.f ? 1.f : TimeSteadyStillness / stillnessCalibrationEaseInTime;
815
816
const Vec calibratedGyro = MinMaxWindow.GetMidGyro();
817
818
const Vec oldGyroBias = Vec(CalibrationData->X, CalibrationData->Y, CalibrationData->Z) / std::max((float)CalibrationData->NumSamples, 1.f);
819
const float stillnessLerpFactor = stillnessCalibrationHalfTime <= 0.f ? 0.f : exp2f(-calibrationEaseIn * deltaTime / stillnessCalibrationHalfTime);
820
Vec newGyroBias = calibratedGyro.Lerp(oldGyroBias, stillnessLerpFactor);
821
Confidence = std::min(Confidence + deltaTime * stillnessConfidenceRate, 1.f);
822
isSteady = true;
823
824
if (doSensorFusion)
825
{
826
const Vec previousNormal = MinMaxWindow.StartAccel.Normalized();
827
const Vec thisNormal = inAccel.Normalized();
828
Vec angularVelocity = thisNormal.Cross(previousNormal);
829
const float crossLength = angularVelocity.Length();
830
if (crossLength > 0.f)
831
{
832
const float thisDotPrev = std::clamp(thisNormal.Dot(previousNormal), -1.f, 1.f);
833
const float angleChange = acosf(thisDotPrev) * 180.0f / (float)M_PI;
834
const float anglePerSecond = angleChange / MinMaxWindow.TimeSampled;
835
angularVelocity *= anglePerSecond / crossLength;
836
}
837
838
Vec axisCalibrationStrength = thisNormal.Abs();
839
Vec sensorFusionBias = (calibratedGyro - angularVelocity).Lerp(oldGyroBias, stillnessLerpFactor);
840
if (axisCalibrationStrength.x <= 0.7f)
841
{
842
newGyroBias.x = sensorFusionBias.x;
843
}
844
if (axisCalibrationStrength.y <= 0.7f)
845
{
846
newGyroBias.y = sensorFusionBias.y;
847
}
848
if (axisCalibrationStrength.z <= 0.7f)
849
{
850
newGyroBias.z = sensorFusionBias.z;
851
}
852
}
853
854
CalibrationData->X = newGyroBias.x;
855
CalibrationData->Y = newGyroBias.y;
856
CalibrationData->Z = newGyroBias.z;
857
858
CalibrationData->AccelMagnitude = MinMaxWindow.MeanAccel.Length();
859
CalibrationData->NumSamples = 1;
860
861
calibrated = true;
862
}
863
else
864
{
865
RecalibrateThreshold = std::min(RecalibrateThreshold + stillnessErrorClimbRate * deltaTime, maxStillnessError);
866
}
867
}
868
else if (TimeSteadyStillness > 0.f)
869
{
870
//printf("Moved!\n");
871
RecalibrateThreshold -= stillnessErrorDropOnRecalibrate;
872
if (RecalibrateThreshold < 1.f) RecalibrateThreshold = 1.f;
873
874
TimeSteadyStillness = 0.f;
875
MinMaxWindow.Reset(0.f);
876
}
877
else
878
{
879
RecalibrateThreshold = std::min(RecalibrateThreshold + stillnessErrorClimbRate * deltaTime, maxStillnessError);
880
MinMaxWindow.Reset(0.f);
881
}
882
883
bIsSteady = isSteady;
884
return calibrated;
885
}
886
887
inline void AutoCalibration::NoSampleStillness()
888
{
889
MinMaxWindow.Reset(0.f);
890
}
891
892
inline bool AutoCalibration::AddSampleSensorFusion(const Vec& inGyro, const Vec& inAccel, float deltaTime)
893
{
894
if (deltaTime <= 0.f)
895
{
896
return false;
897
}
898
899
if (inGyro.x == 0.f && inGyro.y == 0.f && inGyro.z == 0.f &&
900
inAccel.x == 0.f && inAccel.y == 0.f && inAccel.z == 0.f)
901
{
902
// all zeroes are almost certainly not valid inputs
903
TimeSteadySensorFusion = 0.f;
904
SensorFusionSkippedTime = 0.f;
905
PreviousAccel = inAccel;
906
SmoothedPreviousAccel = inAccel;
907
SmoothedAngularVelocityGyro = GamepadMotionHelpers::Vec();
908
SmoothedAngularVelocityAccel = GamepadMotionHelpers::Vec();
909
return false;
910
}
911
912
if (PreviousAccel.x == 0.f && PreviousAccel.y == 0.f && PreviousAccel.z == 0.f)
913
{
914
TimeSteadySensorFusion = 0.f;
915
SensorFusionSkippedTime = 0.f;
916
PreviousAccel = inAccel;
917
SmoothedPreviousAccel = inAccel;
918
SmoothedAngularVelocityGyro = GamepadMotionHelpers::Vec();
919
SmoothedAngularVelocityAccel = GamepadMotionHelpers::Vec();
920
return false;
921
}
922
923
// in case the controller state hasn't updated between samples
924
if (inAccel.x == PreviousAccel.x && inAccel.y == PreviousAccel.y && inAccel.z == PreviousAccel.z)
925
{
926
SensorFusionSkippedTime += deltaTime;
927
return false;
928
}
929
930
if (!Settings)
931
{
932
return false;
933
}
934
935
// get settings
936
const float sensorFusionCalibrationSmoothingStrength = Settings->SensorFusionCalibrationSmoothingStrength;
937
const float sensorFusionAngularAccelerationThreshold = Settings->SensorFusionAngularAccelerationThreshold;
938
const float sensorFusionCalibrationEaseInTime = Settings->SensorFusionCalibrationEaseInTime;
939
const float sensorFusionCalibrationHalfTime = Settings->SensorFusionCalibrationHalfTime * Confidence;
940
const float sensorFusionConfidenceRate = Settings->SensorFusionConfidenceRate;
941
942
deltaTime += SensorFusionSkippedTime;
943
SensorFusionSkippedTime = 0.f;
944
bool calibrated = false;
945
bool isSteady = false;
946
947
// framerate independent lerp smoothing: https://www.gamasutra.com/blogs/ScottLembcke/20180404/316046/Improved_Lerp_Smoothing.php
948
const float smoothingLerpFactor = exp2f(-sensorFusionCalibrationSmoothingStrength * deltaTime);
949
// velocity from smoothed accel matches better if we also smooth gyro
950
const Vec previousGyro = SmoothedAngularVelocityGyro;
951
SmoothedAngularVelocityGyro = inGyro.Lerp(SmoothedAngularVelocityGyro, smoothingLerpFactor); // smooth what remains
952
const float gyroAccelerationMag = (SmoothedAngularVelocityGyro - previousGyro).Length() / deltaTime;
953
// get angle between old and new accel
954
const Vec previousNormal = SmoothedPreviousAccel.Normalized();
955
const Vec thisAccel = inAccel.Lerp(SmoothedPreviousAccel, smoothingLerpFactor);
956
const Vec thisNormal = thisAccel.Normalized();
957
Vec angularVelocity = thisNormal.Cross(previousNormal);
958
const float crossLength = angularVelocity.Length();
959
if (crossLength > 0.f)
960
{
961
const float thisDotPrev = std::clamp(thisNormal.Dot(previousNormal), -1.f, 1.f);
962
const float angleChange = acosf(thisDotPrev) * 180.0f / (float)M_PI;
963
const float anglePerSecond = angleChange / deltaTime;
964
angularVelocity *= anglePerSecond / crossLength;
965
}
966
SmoothedAngularVelocityAccel = angularVelocity;
967
968
// apply corrections
969
if (gyroAccelerationMag > sensorFusionAngularAccelerationThreshold || CalibrationData == nullptr)
970
{
971
TimeSteadySensorFusion = 0.f;
972
//printf("No calibration due to acceleration of %.4f\n", gyroAccelerationMag);
973
}
974
else
975
{
976
TimeSteadySensorFusion = std::min(TimeSteadySensorFusion + deltaTime, sensorFusionCalibrationEaseInTime);
977
const float calibrationEaseIn = sensorFusionCalibrationEaseInTime <= 0.f ? 1.f : TimeSteadySensorFusion / sensorFusionCalibrationEaseInTime;
978
const Vec oldGyroBias = Vec(CalibrationData->X, CalibrationData->Y, CalibrationData->Z) / std::max((float)CalibrationData->NumSamples, 1.f);
979
// recalibrate over time proportional to the difference between the calculated bias and the current assumed bias
980
const float sensorFusionLerpFactor = sensorFusionCalibrationHalfTime <= 0.f ? 0.f : exp2f(-calibrationEaseIn * deltaTime / sensorFusionCalibrationHalfTime);
981
Vec newGyroBias = (SmoothedAngularVelocityGyro - SmoothedAngularVelocityAccel).Lerp(oldGyroBias, sensorFusionLerpFactor);
982
Confidence = std::min(Confidence + deltaTime * sensorFusionConfidenceRate, 1.f);
983
isSteady = true;
984
// don't change bias in axes that can't be affected by the gravity direction
985
Vec axisCalibrationStrength = thisNormal.Abs();
986
if (axisCalibrationStrength.x > 0.7f)
987
{
988
axisCalibrationStrength.x = 1.f;
989
}
990
if (axisCalibrationStrength.y > 0.7f)
991
{
992
axisCalibrationStrength.y = 1.f;
993
}
994
if (axisCalibrationStrength.z > 0.7f)
995
{
996
axisCalibrationStrength.z = 1.f;
997
}
998
newGyroBias = newGyroBias.Lerp(oldGyroBias, axisCalibrationStrength.Min(Vec(1.f)));
999
1000
CalibrationData->X = newGyroBias.x;
1001
CalibrationData->Y = newGyroBias.y;
1002
CalibrationData->Z = newGyroBias.z;
1003
1004
CalibrationData->AccelMagnitude = thisAccel.Length();
1005
1006
CalibrationData->NumSamples = 1;
1007
1008
calibrated = true;
1009
1010
//printf("Recalibrating at a strength of %.4f\n", calibrationEaseIn);
1011
}
1012
1013
SmoothedPreviousAccel = thisAccel;
1014
PreviousAccel = inAccel;
1015
1016
//printf("Gyro: %.4f, %.4f, %.4f | Accel: %.4f, %.4f, %.4f\n",
1017
// SmoothedAngularVelocityGyro.x, SmoothedAngularVelocityGyro.y, SmoothedAngularVelocityGyro.z,
1018
// SmoothedAngularVelocityAccel.x, SmoothedAngularVelocityAccel.y, SmoothedAngularVelocityAccel.z);
1019
1020
bIsSteady = isSteady;
1021
1022
return calibrated;
1023
}
1024
1025
inline void AutoCalibration::NoSampleSensorFusion()
1026
{
1027
TimeSteadySensorFusion = 0.f;
1028
SensorFusionSkippedTime = 0.f;
1029
PreviousAccel = GamepadMotionHelpers::Vec();
1030
SmoothedPreviousAccel = GamepadMotionHelpers::Vec();
1031
SmoothedAngularVelocityGyro = GamepadMotionHelpers::Vec();
1032
SmoothedAngularVelocityAccel = GamepadMotionHelpers::Vec();
1033
}
1034
1035
inline void AutoCalibration::SetCalibrationData(GyroCalibration* calibrationData)
1036
{
1037
CalibrationData = calibrationData;
1038
}
1039
1040
inline void AutoCalibration::SetSettings(GamepadMotionSettings* settings)
1041
{
1042
Settings = settings;
1043
}
1044
1045
} // namespace GamepadMotionHelpers
1046
1047
inline GamepadMotion::GamepadMotion()
1048
{
1049
IsCalibrating = false;
1050
CurrentCalibrationMode = GamepadMotionHelpers::CalibrationMode::Manual;
1051
Reset();
1052
AutoCalibration.SetCalibrationData(&GyroCalibration);
1053
AutoCalibration.SetSettings(&Settings);
1054
Motion.SetSettings(&Settings);
1055
}
1056
1057
inline void GamepadMotion::Reset()
1058
{
1059
GyroCalibration = {};
1060
Gyro = {};
1061
RawAccel = {};
1062
Settings = GamepadMotionSettings();
1063
Motion.Reset();
1064
}
1065
1066
inline void GamepadMotion::ProcessMotion(float gyroX, float gyroY, float gyroZ,
1067
float accelX, float accelY, float accelZ, float deltaTime)
1068
{
1069
if (gyroX == 0.f && gyroY == 0.f && gyroZ == 0.f &&
1070
accelX == 0.f && accelY == 0.f && accelZ == 0.f)
1071
{
1072
// all zeroes are almost certainly not valid inputs
1073
return;
1074
}
1075
1076
float accelMagnitude = sqrtf(accelX * accelX + accelY * accelY + accelZ * accelZ);
1077
1078
if (IsCalibrating)
1079
{
1080
// manual calibration
1081
PushSensorSamples(gyroX, gyroY, gyroZ, accelMagnitude);
1082
AutoCalibration.NoSampleSensorFusion();
1083
AutoCalibration.NoSampleStillness();
1084
}
1085
else if (CurrentCalibrationMode & GamepadMotionHelpers::CalibrationMode::Stillness)
1086
{
1087
AutoCalibration.AddSampleStillness(GamepadMotionHelpers::Vec(gyroX, gyroY, gyroZ), GamepadMotionHelpers::Vec(accelX, accelY, accelZ), deltaTime, CurrentCalibrationMode & GamepadMotionHelpers::CalibrationMode::SensorFusion);
1088
AutoCalibration.NoSampleSensorFusion();
1089
}
1090
else
1091
{
1092
AutoCalibration.NoSampleStillness();
1093
if (CurrentCalibrationMode & GamepadMotionHelpers::CalibrationMode::SensorFusion)
1094
{
1095
AutoCalibration.AddSampleSensorFusion(GamepadMotionHelpers::Vec(gyroX, gyroY, gyroZ), GamepadMotionHelpers::Vec(accelX, accelY, accelZ), deltaTime);
1096
}
1097
else
1098
{
1099
AutoCalibration.NoSampleSensorFusion();
1100
}
1101
}
1102
1103
float gyroOffsetX, gyroOffsetY, gyroOffsetZ;
1104
GetCalibratedSensor(gyroOffsetX, gyroOffsetY, gyroOffsetZ, accelMagnitude);
1105
1106
gyroX -= gyroOffsetX;
1107
gyroY -= gyroOffsetY;
1108
gyroZ -= gyroOffsetZ;
1109
1110
Motion.Update(gyroX, gyroY, gyroZ, accelX, accelY, accelZ, accelMagnitude, deltaTime);
1111
1112
Gyro.x = gyroX;
1113
Gyro.y = gyroY;
1114
Gyro.z = gyroZ;
1115
RawAccel.x = accelX;
1116
RawAccel.y = accelY;
1117
RawAccel.z = accelZ;
1118
}
1119
1120
// reading the current state
1121
inline void GamepadMotion::GetCalibratedGyro(float& x, float& y, float& z)
1122
{
1123
x = Gyro.x;
1124
y = Gyro.y;
1125
z = Gyro.z;
1126
}
1127
1128
inline void GamepadMotion::GetGravity(float& x, float& y, float& z)
1129
{
1130
x = Motion.Grav.x;
1131
y = Motion.Grav.y;
1132
z = Motion.Grav.z;
1133
}
1134
1135
inline void GamepadMotion::GetProcessedAcceleration(float& x, float& y, float& z)
1136
{
1137
x = Motion.Accel.x;
1138
y = Motion.Accel.y;
1139
z = Motion.Accel.z;
1140
}
1141
1142
inline void GamepadMotion::GetOrientation(float& w, float& x, float& y, float& z)
1143
{
1144
w = Motion.Quaternion.w;
1145
x = Motion.Quaternion.x;
1146
y = Motion.Quaternion.y;
1147
z = Motion.Quaternion.z;
1148
}
1149
1150
inline void GamepadMotion::GetPlayerSpaceGyro(float& x, float& y, const float yawRelaxFactor)
1151
{
1152
CalculatePlayerSpaceGyro(x, y, Gyro.x, Gyro.y, Gyro.z, Motion.Grav.x, Motion.Grav.y, Motion.Grav.z, yawRelaxFactor);
1153
}
1154
1155
inline void GamepadMotion::CalculatePlayerSpaceGyro(float& x, float& y, const float gyroX, const float gyroY, const float gyroZ, const float gravX, const float gravY, const float gravZ, const float yawRelaxFactor)
1156
{
1157
// take gravity into account without taking on any error from gravity. Explained in depth at http://gyrowiki.jibbsmart.com/blog:player-space-gyro-and-alternatives-explained#toc7
1158
const float worldYaw = -(gravY * gyroY + gravZ * gyroZ);
1159
const float worldYawSign = worldYaw < 0.f ? -1.f : 1.f;
1160
y = worldYawSign * std::min(std::abs(worldYaw) * yawRelaxFactor, sqrtf(gyroY * gyroY + gyroZ * gyroZ));
1161
x = gyroX;
1162
}
1163
1164
inline void GamepadMotion::GetWorldSpaceGyro(float& x, float& y, const float sideReductionThreshold)
1165
{
1166
CalculateWorldSpaceGyro(x, y, Gyro.x, Gyro.y, Gyro.z, Motion.Grav.x, Motion.Grav.y, Motion.Grav.z, sideReductionThreshold);
1167
}
1168
1169
inline void GamepadMotion::CalculateWorldSpaceGyro(float& x, float& y, const float gyroX, const float gyroY, const float gyroZ, const float gravX, const float gravY, const float gravZ, const float sideReductionThreshold)
1170
{
1171
// use the gravity direction as the yaw axis, and derive an appropriate pitch axis. Explained in depth at http://gyrowiki.jibbsmart.com/blog:player-space-gyro-and-alternatives-explained#toc6
1172
const float worldYaw = -gravX * gyroX - gravY * gyroY - gravZ * gyroZ;
1173
// project local pitch axis (X) onto gravity plane
1174
const float gravDotPitchAxis = gravX;
1175
GamepadMotionHelpers::Vec pitchAxis(1.f - gravX * gravDotPitchAxis,
1176
-gravY * gravDotPitchAxis,
1177
-gravZ * gravDotPitchAxis);
1178
// normalize
1179
const float pitchAxisLengthSquared = pitchAxis.LengthSquared();
1180
if (pitchAxisLengthSquared > 0.f)
1181
{
1182
const float pitchAxisLength = sqrtf(pitchAxisLengthSquared);
1183
const float lengthReciprocal = 1.f / pitchAxisLength;
1184
pitchAxis *= lengthReciprocal;
1185
1186
const float flatness = std::abs(gravY);
1187
const float upness = std::abs(gravZ);
1188
const float sideReduction = sideReductionThreshold <= 0.f ? 1.f : std::clamp((std::max(flatness, upness) - sideReductionThreshold) / sideReductionThreshold, 0.f, 1.f);
1189
1190
x = sideReduction * pitchAxis.Dot(GamepadMotionHelpers::Vec(gyroX, gyroY, gyroZ));
1191
}
1192
else
1193
{
1194
x = 0.f;
1195
}
1196
1197
y = worldYaw;
1198
}
1199
1200
// gyro calibration functions
1201
inline void GamepadMotion::StartContinuousCalibration()
1202
{
1203
IsCalibrating = true;
1204
}
1205
1206
inline void GamepadMotion::PauseContinuousCalibration()
1207
{
1208
IsCalibrating = false;
1209
}
1210
1211
inline void GamepadMotion::ResetContinuousCalibration()
1212
{
1213
GyroCalibration = {};
1214
AutoCalibration.Reset();
1215
}
1216
1217
inline void GamepadMotion::GetCalibrationOffset(float& xOffset, float& yOffset, float& zOffset)
1218
{
1219
float accelMagnitude;
1220
GetCalibratedSensor(xOffset, yOffset, zOffset, accelMagnitude);
1221
}
1222
1223
inline void GamepadMotion::SetCalibrationOffset(float xOffset, float yOffset, float zOffset, int weight)
1224
{
1225
if (GyroCalibration.NumSamples > 1)
1226
{
1227
GyroCalibration.AccelMagnitude *= ((float)weight) / GyroCalibration.NumSamples;
1228
}
1229
else
1230
{
1231
GyroCalibration.AccelMagnitude = (float)weight;
1232
}
1233
1234
GyroCalibration.NumSamples = weight;
1235
GyroCalibration.X = xOffset * weight;
1236
GyroCalibration.Y = yOffset * weight;
1237
GyroCalibration.Z = zOffset * weight;
1238
}
1239
1240
inline float GamepadMotion::GetAutoCalibrationConfidence()
1241
{
1242
return AutoCalibration.Confidence;
1243
}
1244
1245
inline void GamepadMotion::SetAutoCalibrationConfidence(float newConfidence)
1246
{
1247
AutoCalibration.Confidence = newConfidence;
1248
}
1249
1250
inline bool GamepadMotion::GetAutoCalibrationIsSteady()
1251
{
1252
return AutoCalibration.IsSteady();
1253
}
1254
1255
inline GamepadMotionHelpers::CalibrationMode GamepadMotion::GetCalibrationMode()
1256
{
1257
return CurrentCalibrationMode;
1258
}
1259
1260
inline void GamepadMotion::SetCalibrationMode(GamepadMotionHelpers::CalibrationMode calibrationMode)
1261
{
1262
CurrentCalibrationMode = calibrationMode;
1263
}
1264
1265
inline void GamepadMotion::ResetMotion()
1266
{
1267
Motion.Reset();
1268
}
1269
1270
// Private Methods
1271
1272
inline void GamepadMotion::PushSensorSamples(float gyroX, float gyroY, float gyroZ, float accelMagnitude)
1273
{
1274
// accumulate
1275
GyroCalibration.NumSamples++;
1276
GyroCalibration.X += gyroX;
1277
GyroCalibration.Y += gyroY;
1278
GyroCalibration.Z += gyroZ;
1279
GyroCalibration.AccelMagnitude += accelMagnitude;
1280
}
1281
1282
inline void GamepadMotion::GetCalibratedSensor(float& gyroOffsetX, float& gyroOffsetY, float& gyroOffsetZ, float& accelMagnitude)
1283
{
1284
if (GyroCalibration.NumSamples <= 0)
1285
{
1286
gyroOffsetX = 0.f;
1287
gyroOffsetY = 0.f;
1288
gyroOffsetZ = 0.f;
1289
accelMagnitude = 1.f;
1290
return;
1291
}
1292
1293
const float inverseSamples = 1.f / GyroCalibration.NumSamples;
1294
gyroOffsetX = GyroCalibration.X * inverseSamples;
1295
gyroOffsetY = GyroCalibration.Y * inverseSamples;
1296
gyroOffsetZ = GyroCalibration.Z * inverseSamples;
1297
accelMagnitude = GyroCalibration.AccelMagnitude * inverseSamples;
1298
}
1299
1300