rev |
line source |
nuclear@1
|
1 /************************************************************************************
|
nuclear@1
|
2
|
nuclear@1
|
3 Filename : OVR_SensorFusion.cpp
|
nuclear@1
|
4 Content : Methods that determine head orientation from sensor data over time
|
nuclear@1
|
5 Created : October 9, 2012
|
nuclear@1
|
6 Authors : Michael Antonov, Steve LaValle
|
nuclear@1
|
7
|
nuclear@1
|
8 Copyright : Copyright 2012 Oculus VR, Inc. All Rights reserved.
|
nuclear@1
|
9
|
nuclear@1
|
10 Use of this software is subject to the terms of the Oculus license
|
nuclear@1
|
11 agreement provided at the time of installation or download, or which
|
nuclear@1
|
12 otherwise accompanies this software in either electronic or hard copy form.
|
nuclear@1
|
13
|
nuclear@1
|
14 *************************************************************************************/
|
nuclear@1
|
15
|
nuclear@1
|
16 #include "OVR_SensorFusion.h"
|
nuclear@1
|
17 #include "Kernel/OVR_Log.h"
|
nuclear@1
|
18 #include "Kernel/OVR_System.h"
|
nuclear@1
|
19 #include "OVR_JSON.h"
|
nuclear@1
|
20 #include "OVR_Profile.h"
|
nuclear@1
|
21
|
nuclear@1
|
22 namespace OVR {
|
nuclear@1
|
23
|
nuclear@1
|
24 //-------------------------------------------------------------------------------------
|
nuclear@1
|
25 // ***** Sensor Fusion
|
nuclear@1
|
26
|
nuclear@1
|
27 SensorFusion::SensorFusion(SensorDevice* sensor)
|
nuclear@1
|
28 : Handler(getThis()), pDelegate(0),
|
nuclear@1
|
29 Gain(0.05f), YawMult(1), EnableGravity(true), Stage(0), RunningTime(0), DeltaT(0.001f),
|
nuclear@1
|
30 EnablePrediction(true), PredictionDT(0.03f), PredictionTimeIncrement(0.001f),
|
nuclear@1
|
31 FRawMag(10), FAccW(20), FAngV(20),
|
nuclear@1
|
32 TiltCondCount(0), TiltErrorAngle(0),
|
nuclear@1
|
33 TiltErrorAxis(0,1,0),
|
nuclear@1
|
34 MagCondCount(0), MagCalibrated(false), MagRefQ(0, 0, 0, 1),
|
nuclear@1
|
35 MagRefM(0), MagRefYaw(0), YawErrorAngle(0), MagRefDistance(0.5f),
|
nuclear@1
|
36 YawErrorCount(0), YawCorrectionActivated(false), YawCorrectionInProgress(false),
|
nuclear@1
|
37 EnableYawCorrection(false), MagNumReferences(0), MagHasNearbyReference(false),
|
nuclear@1
|
38 MotionTrackingEnabled(true)
|
nuclear@1
|
39 {
|
nuclear@1
|
40 if (sensor)
|
nuclear@1
|
41 AttachToSensor(sensor);
|
nuclear@1
|
42 MagCalibrationMatrix.SetIdentity();
|
nuclear@1
|
43 }
|
nuclear@1
|
44
|
nuclear@1
|
45 SensorFusion::~SensorFusion()
|
nuclear@1
|
46 {
|
nuclear@1
|
47 }
|
nuclear@1
|
48
|
nuclear@1
|
49
|
nuclear@1
|
50 bool SensorFusion::AttachToSensor(SensorDevice* sensor)
|
nuclear@1
|
51 {
|
nuclear@1
|
52 // clear the cached device information
|
nuclear@1
|
53 CachedSensorInfo.SerialNumber[0] = 0;
|
nuclear@1
|
54 CachedSensorInfo.VendorId = 0;
|
nuclear@1
|
55 CachedSensorInfo.ProductId = 0;
|
nuclear@1
|
56
|
nuclear@1
|
57 if (sensor != NULL)
|
nuclear@1
|
58 {
|
nuclear@1
|
59 // Cache the sensor device so we can access this information during
|
nuclear@1
|
60 // mag saving and loading (avoid holding a reference to sensor to prevent
|
nuclear@1
|
61 // deadlock on shutdown)
|
nuclear@1
|
62 sensor->GetDeviceInfo(&CachedSensorInfo); // save the device information
|
nuclear@1
|
63 MessageHandler* pCurrentHandler = sensor->GetMessageHandler();
|
nuclear@1
|
64
|
nuclear@1
|
65 if (pCurrentHandler == &Handler)
|
nuclear@1
|
66 {
|
nuclear@1
|
67 Reset();
|
nuclear@1
|
68 return true;
|
nuclear@1
|
69 }
|
nuclear@1
|
70
|
nuclear@1
|
71 if (pCurrentHandler != NULL)
|
nuclear@1
|
72 {
|
nuclear@1
|
73 OVR_DEBUG_LOG(
|
nuclear@1
|
74 ("SensorFusion::AttachToSensor failed - sensor %p already has handler", sensor));
|
nuclear@1
|
75 return false;
|
nuclear@1
|
76 }
|
nuclear@1
|
77
|
nuclear@1
|
78 // Automatically load the default mag calibration for this sensor
|
nuclear@1
|
79 LoadMagCalibration();
|
nuclear@1
|
80 }
|
nuclear@1
|
81
|
nuclear@1
|
82 if (Handler.IsHandlerInstalled())
|
nuclear@1
|
83 {
|
nuclear@1
|
84 Handler.RemoveHandlerFromDevices();
|
nuclear@1
|
85 }
|
nuclear@1
|
86
|
nuclear@1
|
87 if (sensor != NULL)
|
nuclear@1
|
88 {
|
nuclear@1
|
89 sensor->SetMessageHandler(&Handler);
|
nuclear@1
|
90 }
|
nuclear@1
|
91
|
nuclear@1
|
92 Reset();
|
nuclear@1
|
93 return true;
|
nuclear@1
|
94 }
|
nuclear@1
|
95
|
nuclear@1
|
96
|
nuclear@1
|
97 // Resets the current orientation
|
nuclear@1
|
98 void SensorFusion::Reset()
|
nuclear@1
|
99 {
|
nuclear@1
|
100 Lock::Locker lockScope(Handler.GetHandlerLock());
|
nuclear@1
|
101 Q = Quatf();
|
nuclear@1
|
102 QUncorrected = Quatf();
|
nuclear@1
|
103 Stage = 0;
|
nuclear@1
|
104 RunningTime = 0;
|
nuclear@1
|
105 MagNumReferences = 0;
|
nuclear@1
|
106 MagHasNearbyReference = false;
|
nuclear@1
|
107 }
|
nuclear@1
|
108
|
nuclear@1
|
109
|
nuclear@1
|
110 void SensorFusion::handleMessage(const MessageBodyFrame& msg)
|
nuclear@1
|
111 {
|
nuclear@1
|
112 if (msg.Type != Message_BodyFrame || !IsMotionTrackingEnabled())
|
nuclear@1
|
113 return;
|
nuclear@1
|
114
|
nuclear@1
|
115 // Put the sensor readings into convenient local variables
|
nuclear@1
|
116 Vector3f angVel = msg.RotationRate;
|
nuclear@1
|
117 Vector3f rawAccel = msg.Acceleration;
|
nuclear@1
|
118 Vector3f mag = msg.MagneticField;
|
nuclear@1
|
119
|
nuclear@1
|
120 // Set variables accessible through the class API
|
nuclear@1
|
121 DeltaT = msg.TimeDelta;
|
nuclear@1
|
122 AngV = msg.RotationRate;
|
nuclear@1
|
123 AngV.y *= YawMult; // Warning: If YawMult != 1, then AngV is not true angular velocity
|
nuclear@1
|
124 A = rawAccel;
|
nuclear@1
|
125
|
nuclear@1
|
126 // Allow external access to uncalibrated magnetometer values
|
nuclear@1
|
127 RawMag = mag;
|
nuclear@1
|
128
|
nuclear@1
|
129 // Apply the calibration parameters to raw mag
|
nuclear@1
|
130 if (HasMagCalibration())
|
nuclear@1
|
131 {
|
nuclear@1
|
132 mag.x += MagCalibrationMatrix.M[0][3];
|
nuclear@1
|
133 mag.y += MagCalibrationMatrix.M[1][3];
|
nuclear@1
|
134 mag.z += MagCalibrationMatrix.M[2][3];
|
nuclear@1
|
135 }
|
nuclear@1
|
136
|
nuclear@1
|
137 // Provide external access to calibrated mag values
|
nuclear@1
|
138 // (if the mag is not calibrated, then the raw value is returned)
|
nuclear@1
|
139 CalMag = mag;
|
nuclear@1
|
140
|
nuclear@1
|
141 float angVelLength = angVel.Length();
|
nuclear@1
|
142 float accLength = rawAccel.Length();
|
nuclear@1
|
143
|
nuclear@1
|
144
|
nuclear@1
|
145 // Acceleration in the world frame (Q is current HMD orientation)
|
nuclear@1
|
146 Vector3f accWorld = Q.Rotate(rawAccel);
|
nuclear@1
|
147
|
nuclear@1
|
148 // Keep track of time
|
nuclear@1
|
149 Stage++;
|
nuclear@1
|
150 RunningTime += DeltaT;
|
nuclear@1
|
151
|
nuclear@1
|
152 // Insert current sensor data into filter history
|
nuclear@1
|
153 FRawMag.AddElement(RawMag);
|
nuclear@1
|
154 FAccW.AddElement(accWorld);
|
nuclear@1
|
155 FAngV.AddElement(angVel);
|
nuclear@1
|
156
|
nuclear@1
|
157 // Update orientation Q based on gyro outputs. This technique is
|
nuclear@1
|
158 // based on direct properties of the angular velocity vector:
|
nuclear@1
|
159 // Its direction is the current rotation axis, and its magnitude
|
nuclear@1
|
160 // is the rotation rate (rad/sec) about that axis. Our sensor
|
nuclear@1
|
161 // sampling rate is so fast that we need not worry about integral
|
nuclear@1
|
162 // approximation error (not yet, anyway).
|
nuclear@1
|
163 if (angVelLength > 0.0f)
|
nuclear@1
|
164 {
|
nuclear@1
|
165 Vector3f rotAxis = angVel / angVelLength;
|
nuclear@1
|
166 float halfRotAngle = angVelLength * DeltaT * 0.5f;
|
nuclear@1
|
167 float sinHRA = sin(halfRotAngle);
|
nuclear@1
|
168 Quatf deltaQ(rotAxis.x*sinHRA, rotAxis.y*sinHRA, rotAxis.z*sinHRA, cos(halfRotAngle));
|
nuclear@1
|
169
|
nuclear@1
|
170 Q = Q * deltaQ;
|
nuclear@1
|
171 }
|
nuclear@1
|
172
|
nuclear@1
|
173 // The quaternion magnitude may slowly drift due to numerical error,
|
nuclear@1
|
174 // so it is periodically normalized.
|
nuclear@1
|
175 if (Stage % 5000 == 0)
|
nuclear@1
|
176 Q.Normalize();
|
nuclear@1
|
177
|
nuclear@1
|
178 // Maintain the uncorrected orientation for later use by predictive filtering
|
nuclear@1
|
179 QUncorrected = Q;
|
nuclear@1
|
180
|
nuclear@1
|
181 // Perform tilt correction using the accelerometer data. This enables
|
nuclear@1
|
182 // drift errors in pitch and roll to be corrected. Note that yaw cannot be corrected
|
nuclear@1
|
183 // because the rotation axis is parallel to the gravity vector.
|
nuclear@1
|
184 if (EnableGravity)
|
nuclear@1
|
185 {
|
nuclear@1
|
186 // Correcting for tilt error by using accelerometer data
|
nuclear@1
|
187 const float gravityEpsilon = 0.4f;
|
nuclear@1
|
188 const float angVelEpsilon = 0.1f; // Relatively slow rotation
|
nuclear@1
|
189 const int tiltPeriod = 50; // Required time steps of stability
|
nuclear@1
|
190 const float maxTiltError = 0.05f;
|
nuclear@1
|
191 const float minTiltError = 0.01f;
|
nuclear@1
|
192
|
nuclear@1
|
193 // This condition estimates whether the only measured acceleration is due to gravity
|
nuclear@1
|
194 // (the Rift is not linearly accelerating). It is often wrong, but tends to average
|
nuclear@1
|
195 // out well over time.
|
nuclear@1
|
196 if ((fabs(accLength - 9.81f) < gravityEpsilon) &&
|
nuclear@1
|
197 (angVelLength < angVelEpsilon))
|
nuclear@1
|
198 TiltCondCount++;
|
nuclear@1
|
199 else
|
nuclear@1
|
200 TiltCondCount = 0;
|
nuclear@1
|
201
|
nuclear@1
|
202 // After stable measurements have been taken over a sufficiently long period,
|
nuclear@1
|
203 // estimate the amount of tilt error and calculate the tilt axis for later correction.
|
nuclear@1
|
204 if (TiltCondCount >= tiltPeriod)
|
nuclear@1
|
205 { // Update TiltErrorEstimate
|
nuclear@1
|
206 TiltCondCount = 0;
|
nuclear@1
|
207 // Use an average value to reduce noise (could alternatively use an LPF)
|
nuclear@1
|
208 Vector3f accWMean = FAccW.Mean();
|
nuclear@1
|
209 // Project the acceleration vector into the XZ plane
|
nuclear@1
|
210 Vector3f xzAcc = Vector3f(accWMean.x, 0.0f, accWMean.z);
|
nuclear@1
|
211 // The unit normal of xzAcc will be the rotation axis for tilt correction
|
nuclear@1
|
212 Vector3f tiltAxis = Vector3f(xzAcc.z, 0.0f, -xzAcc.x).Normalized();
|
nuclear@1
|
213 Vector3f yUp = Vector3f(0.0f, 1.0f, 0.0f);
|
nuclear@1
|
214 // This is the amount of rotation
|
nuclear@1
|
215 float tiltAngle = yUp.Angle(accWMean);
|
nuclear@1
|
216 // Record values if the tilt error is intolerable
|
nuclear@1
|
217 if (tiltAngle > maxTiltError)
|
nuclear@1
|
218 {
|
nuclear@1
|
219 TiltErrorAngle = tiltAngle;
|
nuclear@1
|
220 TiltErrorAxis = tiltAxis;
|
nuclear@1
|
221 }
|
nuclear@1
|
222 }
|
nuclear@1
|
223
|
nuclear@1
|
224 // This part performs the actual tilt correction as needed
|
nuclear@1
|
225 if (TiltErrorAngle > minTiltError)
|
nuclear@1
|
226 {
|
nuclear@1
|
227 if ((TiltErrorAngle > 0.4f)&&(RunningTime < 8.0f))
|
nuclear@1
|
228 { // Tilt completely to correct orientation
|
nuclear@1
|
229 Q = Quatf(TiltErrorAxis, -TiltErrorAngle) * Q;
|
nuclear@1
|
230 TiltErrorAngle = 0.0f;
|
nuclear@1
|
231 }
|
nuclear@1
|
232 else
|
nuclear@1
|
233 {
|
nuclear@1
|
234 //LogText("Performing tilt correction - Angle: %f Axis: %f %f %f\n",
|
nuclear@1
|
235 // TiltErrorAngle,TiltErrorAxis.x,TiltErrorAxis.y,TiltErrorAxis.z);
|
nuclear@1
|
236 //float deltaTiltAngle = -Gain*TiltErrorAngle*0.005f;
|
nuclear@1
|
237 // This uses aggressive correction steps while your head is moving fast
|
nuclear@1
|
238 float deltaTiltAngle = -Gain*TiltErrorAngle*0.005f*(5.0f*angVelLength+1.0f);
|
nuclear@1
|
239 // Incrementally "un-tilt" by a small step size
|
nuclear@1
|
240 Q = Quatf(TiltErrorAxis, deltaTiltAngle) * Q;
|
nuclear@1
|
241 TiltErrorAngle += deltaTiltAngle;
|
nuclear@1
|
242 }
|
nuclear@1
|
243 }
|
nuclear@1
|
244 }
|
nuclear@1
|
245
|
nuclear@1
|
246 // Yaw drift correction based on magnetometer data. This corrects the part of the drift
|
nuclear@1
|
247 // that the accelerometer cannot handle.
|
nuclear@1
|
248 // This will only work if the magnetometer has been enabled, calibrated, and a reference
|
nuclear@1
|
249 // point has been set.
|
nuclear@1
|
250 const float maxAngVelLength = 3.0f;
|
nuclear@1
|
251 const int magWindow = 5;
|
nuclear@1
|
252 const float yawErrorMax = 0.1f;
|
nuclear@1
|
253 const float yawErrorMin = 0.01f;
|
nuclear@1
|
254 const int yawErrorCountLimit = 50;
|
nuclear@1
|
255 const float yawRotationStep = 0.00002f;
|
nuclear@1
|
256
|
nuclear@1
|
257 if (angVelLength < maxAngVelLength)
|
nuclear@1
|
258 MagCondCount++;
|
nuclear@1
|
259 else
|
nuclear@1
|
260 MagCondCount = 0;
|
nuclear@1
|
261
|
nuclear@1
|
262 // Find, create, and utilize reference points for the magnetometer
|
nuclear@1
|
263 // Need to be careful not to set reference points while there is significant tilt error
|
nuclear@1
|
264 if ((EnableYawCorrection && MagCalibrated)&&(RunningTime > 10.0f)&&(TiltErrorAngle < 0.2f))
|
nuclear@1
|
265 {
|
nuclear@1
|
266 if (MagNumReferences == 0)
|
nuclear@1
|
267 {
|
nuclear@1
|
268 setMagReference(); // Use the current direction
|
nuclear@1
|
269 }
|
nuclear@1
|
270 else if (Q.Distance(MagRefQ) > MagRefDistance)
|
nuclear@1
|
271 {
|
nuclear@1
|
272 MagHasNearbyReference = false;
|
nuclear@1
|
273 float bestDist = 100000.0f;
|
nuclear@1
|
274 int bestNdx = 0;
|
nuclear@1
|
275 float dist;
|
nuclear@1
|
276 for (int i = 0; i < MagNumReferences; i++)
|
nuclear@1
|
277 {
|
nuclear@1
|
278 dist = Q.Distance(MagRefTableQ[i]);
|
nuclear@1
|
279 if (dist < bestDist)
|
nuclear@1
|
280 {
|
nuclear@1
|
281 bestNdx = i;
|
nuclear@1
|
282 bestDist = dist;
|
nuclear@1
|
283 }
|
nuclear@1
|
284 }
|
nuclear@1
|
285
|
nuclear@1
|
286 if (bestDist < MagRefDistance)
|
nuclear@1
|
287 {
|
nuclear@1
|
288 MagHasNearbyReference = true;
|
nuclear@1
|
289 MagRefQ = MagRefTableQ[bestNdx];
|
nuclear@1
|
290 MagRefM = MagRefTableM[bestNdx];
|
nuclear@1
|
291 MagRefYaw = MagRefTableYaw[bestNdx];
|
nuclear@1
|
292 //LogText("Using reference %d\n",bestNdx);
|
nuclear@1
|
293 }
|
nuclear@1
|
294 else if (MagNumReferences < MagMaxReferences)
|
nuclear@1
|
295 setMagReference();
|
nuclear@1
|
296 }
|
nuclear@1
|
297 }
|
nuclear@1
|
298
|
nuclear@1
|
299 YawCorrectionInProgress = false;
|
nuclear@1
|
300 if (EnableYawCorrection && MagCalibrated && (RunningTime > 2.0f) && (MagCondCount >= magWindow) &&
|
nuclear@1
|
301 MagHasNearbyReference)
|
nuclear@1
|
302 {
|
nuclear@1
|
303 // Use rotational invariance to bring reference mag value into global frame
|
nuclear@1
|
304 Vector3f grefmag = MagRefQ.Rotate(GetCalibratedMagValue(MagRefM));
|
nuclear@1
|
305 // Bring current (averaged) mag reading into global frame
|
nuclear@1
|
306 Vector3f gmag = Q.Rotate(GetCalibratedMagValue(FRawMag.Mean()));
|
nuclear@1
|
307 // Calculate the reference yaw in the global frame
|
nuclear@1
|
308 Anglef gryaw = Anglef(atan2(grefmag.x,grefmag.z));
|
nuclear@1
|
309 // Calculate the current yaw in the global frame
|
nuclear@1
|
310 Anglef gyaw = Anglef(atan2(gmag.x,gmag.z));
|
nuclear@1
|
311 // The difference between reference and current yaws is the perceived error
|
nuclear@1
|
312 Anglef YawErrorAngle = gyaw - gryaw;
|
nuclear@1
|
313
|
nuclear@1
|
314 //LogText("Yaw error estimate: %f\n",YawErrorAngle.Get());
|
nuclear@1
|
315 // If the perceived error is large, keep count
|
nuclear@1
|
316 if ((YawErrorAngle.Abs() > yawErrorMax) && (!YawCorrectionActivated))
|
nuclear@1
|
317 YawErrorCount++;
|
nuclear@1
|
318 // After enough iterations of high perceived error, start the correction process
|
nuclear@1
|
319 if (YawErrorCount > yawErrorCountLimit)
|
nuclear@1
|
320 YawCorrectionActivated = true;
|
nuclear@1
|
321 // If the perceived error becomes small, turn off the yaw correction
|
nuclear@1
|
322 if ((YawErrorAngle.Abs() < yawErrorMin) && YawCorrectionActivated)
|
nuclear@1
|
323 {
|
nuclear@1
|
324 YawCorrectionActivated = false;
|
nuclear@1
|
325 YawErrorCount = 0;
|
nuclear@1
|
326 }
|
nuclear@1
|
327
|
nuclear@1
|
328 // Perform the actual yaw correction, due to previously detected, large yaw error
|
nuclear@1
|
329 if (YawCorrectionActivated)
|
nuclear@1
|
330 {
|
nuclear@1
|
331 YawCorrectionInProgress = true;
|
nuclear@1
|
332 // Incrementally "unyaw" by a small step size
|
nuclear@1
|
333 Q = Quatf(Vector3f(0.0f,1.0f,0.0f), -yawRotationStep * YawErrorAngle.Sign()) * Q;
|
nuclear@1
|
334 }
|
nuclear@1
|
335 }
|
nuclear@1
|
336 }
|
nuclear@1
|
337
|
nuclear@1
|
338
|
nuclear@1
|
339 // A predictive filter based on extrapolating the smoothed, current angular velocity
|
nuclear@1
|
340 Quatf SensorFusion::GetPredictedOrientation(float pdt)
|
nuclear@1
|
341 {
|
nuclear@1
|
342 Lock::Locker lockScope(Handler.GetHandlerLock());
|
nuclear@1
|
343 Quatf qP = QUncorrected;
|
nuclear@1
|
344
|
nuclear@1
|
345 if (EnablePrediction)
|
nuclear@1
|
346 {
|
nuclear@1
|
347 // This method assumes a constant angular velocity
|
nuclear@1
|
348 Vector3f angVelF = FAngV.SavitzkyGolaySmooth8();
|
nuclear@1
|
349 float angVelFL = angVelF.Length();
|
nuclear@1
|
350
|
nuclear@1
|
351 // Force back to raw measurement
|
nuclear@1
|
352 angVelF = AngV;
|
nuclear@1
|
353 angVelFL = AngV.Length();
|
nuclear@1
|
354
|
nuclear@1
|
355 // Dynamic prediction interval: Based on angular velocity to reduce vibration
|
nuclear@1
|
356 const float minPdt = 0.001f;
|
nuclear@1
|
357 const float slopePdt = 0.1f;
|
nuclear@1
|
358 float newpdt = pdt;
|
nuclear@1
|
359 float tpdt = minPdt + slopePdt * angVelFL;
|
nuclear@1
|
360 if (tpdt < pdt)
|
nuclear@1
|
361 newpdt = tpdt;
|
nuclear@1
|
362 //LogText("PredictonDTs: %d\n",(int)(newpdt / PredictionTimeIncrement + 0.5f));
|
nuclear@1
|
363
|
nuclear@1
|
364 if (angVelFL > 0.001f)
|
nuclear@1
|
365 {
|
nuclear@1
|
366 Vector3f rotAxisP = angVelF / angVelFL;
|
nuclear@1
|
367 float halfRotAngleP = angVelFL * newpdt * 0.5f;
|
nuclear@1
|
368 float sinaHRAP = sin(halfRotAngleP);
|
nuclear@1
|
369 Quatf deltaQP(rotAxisP.x*sinaHRAP, rotAxisP.y*sinaHRAP,
|
nuclear@1
|
370 rotAxisP.z*sinaHRAP, cos(halfRotAngleP));
|
nuclear@1
|
371 qP = QUncorrected * deltaQP;
|
nuclear@1
|
372 }
|
nuclear@1
|
373 }
|
nuclear@1
|
374 return qP;
|
nuclear@1
|
375 }
|
nuclear@1
|
376
|
nuclear@1
|
377
|
nuclear@1
|
378 Vector3f SensorFusion::GetCalibratedMagValue(const Vector3f& rawMag) const
|
nuclear@1
|
379 {
|
nuclear@1
|
380 Vector3f mag = rawMag;
|
nuclear@1
|
381 OVR_ASSERT(HasMagCalibration());
|
nuclear@1
|
382 mag.x += MagCalibrationMatrix.M[0][3];
|
nuclear@1
|
383 mag.y += MagCalibrationMatrix.M[1][3];
|
nuclear@1
|
384 mag.z += MagCalibrationMatrix.M[2][3];
|
nuclear@1
|
385 return mag;
|
nuclear@1
|
386 }
|
nuclear@1
|
387
|
nuclear@1
|
388
|
nuclear@1
|
389 void SensorFusion::setMagReference(const Quatf& q, const Vector3f& rawMag)
|
nuclear@1
|
390 {
|
nuclear@1
|
391 if (MagNumReferences < MagMaxReferences)
|
nuclear@1
|
392 {
|
nuclear@1
|
393 MagRefTableQ[MagNumReferences] = q;
|
nuclear@1
|
394 MagRefTableM[MagNumReferences] = rawMag; //FRawMag.Mean();
|
nuclear@1
|
395
|
nuclear@1
|
396 //LogText("Inserting reference %d\n",MagNumReferences);
|
nuclear@1
|
397
|
nuclear@1
|
398 MagRefQ = q;
|
nuclear@1
|
399 MagRefM = rawMag;
|
nuclear@1
|
400
|
nuclear@1
|
401 float pitch, roll, yaw;
|
nuclear@1
|
402 Quatf q2 = q;
|
nuclear@1
|
403 q2.GetEulerAngles<Axis_X, Axis_Z, Axis_Y>(&pitch, &roll, &yaw);
|
nuclear@1
|
404 MagRefTableYaw[MagNumReferences] = yaw;
|
nuclear@1
|
405 MagRefYaw = yaw;
|
nuclear@1
|
406
|
nuclear@1
|
407 MagNumReferences++;
|
nuclear@1
|
408
|
nuclear@1
|
409 MagHasNearbyReference = true;
|
nuclear@1
|
410 }
|
nuclear@1
|
411 }
|
nuclear@1
|
412
|
nuclear@1
|
413
|
nuclear@1
|
414 SensorFusion::BodyFrameHandler::~BodyFrameHandler()
|
nuclear@1
|
415 {
|
nuclear@1
|
416 RemoveHandlerFromDevices();
|
nuclear@1
|
417 }
|
nuclear@1
|
418
|
nuclear@1
|
419 void SensorFusion::BodyFrameHandler::OnMessage(const Message& msg)
|
nuclear@1
|
420 {
|
nuclear@1
|
421 if (msg.Type == Message_BodyFrame)
|
nuclear@1
|
422 pFusion->handleMessage(static_cast<const MessageBodyFrame&>(msg));
|
nuclear@1
|
423 if (pFusion->pDelegate)
|
nuclear@1
|
424 pFusion->pDelegate->OnMessage(msg);
|
nuclear@1
|
425 }
|
nuclear@1
|
426
|
nuclear@1
|
427 bool SensorFusion::BodyFrameHandler::SupportsMessageType(MessageType type) const
|
nuclear@1
|
428 {
|
nuclear@1
|
429 return (type == Message_BodyFrame);
|
nuclear@1
|
430 }
|
nuclear@1
|
431
|
nuclear@1
|
432 // Writes the current calibration for a particular device to a device profile file
|
nuclear@1
|
433 // sensor - the sensor that was calibrated
|
nuclear@1
|
434 // cal_name - an optional name for the calibration or default if cal_name == NULL
|
nuclear@1
|
435 bool SensorFusion::SaveMagCalibration(const char* calibrationName) const
|
nuclear@1
|
436 {
|
nuclear@1
|
437 if (CachedSensorInfo.SerialNumber[0] == NULL || !HasMagCalibration())
|
nuclear@1
|
438 return false;
|
nuclear@1
|
439
|
nuclear@1
|
440 // A named calibration may be specified for calibration in different
|
nuclear@1
|
441 // environments, otherwise the default calibration is used
|
nuclear@1
|
442 if (calibrationName == NULL)
|
nuclear@1
|
443 calibrationName = "default";
|
nuclear@1
|
444
|
nuclear@1
|
445 // Generate a mag calibration event
|
nuclear@1
|
446 JSON* calibration = JSON::CreateObject();
|
nuclear@1
|
447 // (hardcoded for now) the measurement and representation method
|
nuclear@1
|
448 calibration->AddStringItem("Version", "1.0");
|
nuclear@1
|
449 calibration->AddStringItem("Name", "default");
|
nuclear@1
|
450
|
nuclear@1
|
451 // time stamp the calibration
|
nuclear@1
|
452 char time_str[64];
|
nuclear@1
|
453
|
nuclear@1
|
454 #ifdef OVR_OS_WIN32
|
nuclear@1
|
455 struct tm caltime;
|
nuclear@1
|
456 localtime_s(&caltime, &MagCalibrationTime);
|
nuclear@1
|
457 strftime(time_str, 64, "%Y-%m-%d %H:%M:%S", &caltime);
|
nuclear@1
|
458 #else
|
nuclear@1
|
459 struct tm* caltime;
|
nuclear@1
|
460 caltime = localtime(&MagCalibrationTime);
|
nuclear@1
|
461 strftime(time_str, 64, "%Y-%m-%d %H:%M:%S", caltime);
|
nuclear@1
|
462 #endif
|
nuclear@1
|
463
|
nuclear@1
|
464 calibration->AddStringItem("Time", time_str);
|
nuclear@1
|
465
|
nuclear@1
|
466 // write the full calibration matrix
|
nuclear@1
|
467 Matrix4f calmat = GetMagCalibration();
|
nuclear@1
|
468 char matrix[128];
|
nuclear@1
|
469 int pos = 0;
|
nuclear@1
|
470 for (int r=0; r<4; r++)
|
nuclear@1
|
471 {
|
nuclear@1
|
472 for (int c=0; c<4; c++)
|
nuclear@1
|
473 {
|
nuclear@1
|
474 pos += (int)OVR_sprintf(matrix+pos, 128, "%g ", calmat.M[r][c]);
|
nuclear@1
|
475 }
|
nuclear@1
|
476 }
|
nuclear@1
|
477 calibration->AddStringItem("Calibration", matrix);
|
nuclear@1
|
478
|
nuclear@1
|
479
|
nuclear@1
|
480 String path = GetBaseOVRPath(true);
|
nuclear@1
|
481 path += "/Devices.json";
|
nuclear@1
|
482
|
nuclear@1
|
483 // Look for a prexisting device file to edit
|
nuclear@1
|
484 Ptr<JSON> root = *JSON::Load(path);
|
nuclear@1
|
485 if (root)
|
nuclear@1
|
486 { // Quick sanity check of the file type and format before we parse it
|
nuclear@1
|
487 JSON* version = root->GetFirstItem();
|
nuclear@1
|
488 if (version && version->Name == "Oculus Device Profile Version")
|
nuclear@1
|
489 { // In the future I may need to check versioning to determine parse method
|
nuclear@1
|
490 }
|
nuclear@1
|
491 else
|
nuclear@1
|
492 {
|
nuclear@1
|
493 root->Release();
|
nuclear@1
|
494 root = NULL;
|
nuclear@1
|
495 }
|
nuclear@1
|
496 }
|
nuclear@1
|
497
|
nuclear@1
|
498 JSON* device = NULL;
|
nuclear@1
|
499 if (root)
|
nuclear@1
|
500 {
|
nuclear@1
|
501 device = root->GetFirstItem(); // skip the header
|
nuclear@1
|
502 device = root->GetNextItem(device);
|
nuclear@1
|
503 while (device)
|
nuclear@1
|
504 { // Search for a previous calibration with the same name for this device
|
nuclear@1
|
505 // and remove it before adding the new one
|
nuclear@1
|
506 if (device->Name == "Device")
|
nuclear@1
|
507 {
|
nuclear@1
|
508 JSON* item = device->GetItemByName("Serial");
|
nuclear@1
|
509 if (item && item->Value == CachedSensorInfo.SerialNumber)
|
nuclear@1
|
510 { // found an entry for this device
|
nuclear@1
|
511 item = device->GetNextItem(item);
|
nuclear@1
|
512 while (item)
|
nuclear@1
|
513 {
|
nuclear@1
|
514 if (item->Name == "MagCalibration")
|
nuclear@1
|
515 {
|
nuclear@1
|
516 JSON* name = item->GetItemByName("Name");
|
nuclear@1
|
517 if (name && name->Value == calibrationName)
|
nuclear@1
|
518 { // found a calibration of the same name
|
nuclear@1
|
519 item->RemoveNode();
|
nuclear@1
|
520 item->Release();
|
nuclear@1
|
521 break;
|
nuclear@1
|
522 }
|
nuclear@1
|
523 }
|
nuclear@1
|
524 item = device->GetNextItem(item);
|
nuclear@1
|
525 }
|
nuclear@1
|
526
|
nuclear@1
|
527 // update the auto-mag flag
|
nuclear@1
|
528 item = device->GetItemByName("EnableYawCorrection");
|
nuclear@1
|
529 if (item)
|
nuclear@1
|
530 item->dValue = (double)EnableYawCorrection;
|
nuclear@1
|
531 else
|
nuclear@1
|
532 device->AddBoolItem("EnableYawCorrection", EnableYawCorrection);
|
nuclear@1
|
533
|
nuclear@1
|
534 break;
|
nuclear@1
|
535 }
|
nuclear@1
|
536 }
|
nuclear@1
|
537
|
nuclear@1
|
538 device = root->GetNextItem(device);
|
nuclear@1
|
539 }
|
nuclear@1
|
540 }
|
nuclear@1
|
541 else
|
nuclear@1
|
542 { // Create a new device root
|
nuclear@1
|
543 root = *JSON::CreateObject();
|
nuclear@1
|
544 root->AddStringItem("Oculus Device Profile Version", "1.0");
|
nuclear@1
|
545 }
|
nuclear@1
|
546
|
nuclear@1
|
547 if (device == NULL)
|
nuclear@1
|
548 {
|
nuclear@1
|
549 device = JSON::CreateObject();
|
nuclear@1
|
550 device->AddStringItem("Product", CachedSensorInfo.ProductName);
|
nuclear@1
|
551 device->AddNumberItem("ProductID", CachedSensorInfo.ProductId);
|
nuclear@1
|
552 device->AddStringItem("Serial", CachedSensorInfo.SerialNumber);
|
nuclear@1
|
553 device->AddBoolItem("EnableYawCorrection", EnableYawCorrection);
|
nuclear@1
|
554
|
nuclear@1
|
555 root->AddItem("Device", device);
|
nuclear@1
|
556 }
|
nuclear@1
|
557
|
nuclear@1
|
558 // Create and the add the new calibration event to the device
|
nuclear@1
|
559 device->AddItem("MagCalibration", calibration);
|
nuclear@1
|
560
|
nuclear@1
|
561 return root->Save(path);
|
nuclear@1
|
562 }
|
nuclear@1
|
563
|
nuclear@1
|
564 // Loads a saved calibration for the specified device from the device profile file
|
nuclear@1
|
565 // sensor - the sensor that the calibration was saved for
|
nuclear@1
|
566 // cal_name - an optional name for the calibration or the default if cal_name == NULL
|
nuclear@1
|
567 bool SensorFusion::LoadMagCalibration(const char* calibrationName)
|
nuclear@1
|
568 {
|
nuclear@1
|
569 if (CachedSensorInfo.SerialNumber[0] == NULL)
|
nuclear@1
|
570 return false;
|
nuclear@1
|
571
|
nuclear@1
|
572 // A named calibration may be specified for calibration in different
|
nuclear@1
|
573 // environments, otherwise the default calibration is used
|
nuclear@1
|
574 if (calibrationName == NULL)
|
nuclear@1
|
575 calibrationName = "default";
|
nuclear@1
|
576
|
nuclear@1
|
577 String path = GetBaseOVRPath(true);
|
nuclear@1
|
578 path += "/Devices.json";
|
nuclear@1
|
579
|
nuclear@1
|
580 // Load the device profiles
|
nuclear@1
|
581 Ptr<JSON> root = *JSON::Load(path);
|
nuclear@1
|
582 if (root == NULL)
|
nuclear@1
|
583 return false;
|
nuclear@1
|
584
|
nuclear@1
|
585 // Quick sanity check of the file type and format before we parse it
|
nuclear@1
|
586 JSON* version = root->GetFirstItem();
|
nuclear@1
|
587 if (version && version->Name == "Oculus Device Profile Version")
|
nuclear@1
|
588 { // In the future I may need to check versioning to determine parse method
|
nuclear@1
|
589 }
|
nuclear@1
|
590 else
|
nuclear@1
|
591 {
|
nuclear@1
|
592 return false;
|
nuclear@1
|
593 }
|
nuclear@1
|
594
|
nuclear@1
|
595 bool autoEnableCorrection = false;
|
nuclear@1
|
596
|
nuclear@1
|
597 JSON* device = root->GetNextItem(version);
|
nuclear@1
|
598 while (device)
|
nuclear@1
|
599 { // Search for a previous calibration with the same name for this device
|
nuclear@1
|
600 // and remove it before adding the new one
|
nuclear@1
|
601 if (device->Name == "Device")
|
nuclear@1
|
602 {
|
nuclear@1
|
603 JSON* item = device->GetItemByName("Serial");
|
nuclear@1
|
604 if (item && item->Value == CachedSensorInfo.SerialNumber)
|
nuclear@1
|
605 { // found an entry for this device
|
nuclear@1
|
606
|
nuclear@1
|
607 JSON* autoyaw = device->GetItemByName("EnableYawCorrection");
|
nuclear@1
|
608 if (autoyaw)
|
nuclear@1
|
609 autoEnableCorrection = (autoyaw->dValue != 0);
|
nuclear@1
|
610
|
nuclear@1
|
611 item = device->GetNextItem(item);
|
nuclear@1
|
612 while (item)
|
nuclear@1
|
613 {
|
nuclear@1
|
614 if (item->Name == "MagCalibration")
|
nuclear@1
|
615 {
|
nuclear@1
|
616 JSON* calibration = item;
|
nuclear@1
|
617 JSON* name = calibration->GetItemByName("Name");
|
nuclear@1
|
618 if (name && name->Value == calibrationName)
|
nuclear@1
|
619 { // found a calibration with this name
|
nuclear@1
|
620
|
nuclear@1
|
621 time_t now;
|
nuclear@1
|
622 time(&now);
|
nuclear@1
|
623
|
nuclear@1
|
624 // parse the calibration time
|
nuclear@1
|
625 time_t calibration_time = now;
|
nuclear@1
|
626 JSON* caltime = calibration->GetItemByName("Time");
|
nuclear@1
|
627 if (caltime)
|
nuclear@1
|
628 {
|
nuclear@1
|
629 const char* caltime_str = caltime->Value.ToCStr();
|
nuclear@1
|
630
|
nuclear@1
|
631 tm ct;
|
nuclear@1
|
632 memset(&ct, 0, sizeof(tm));
|
nuclear@1
|
633
|
nuclear@1
|
634 #ifdef OVR_OS_WIN32
|
nuclear@1
|
635 struct tm nowtime;
|
nuclear@1
|
636 localtime_s(&nowtime, &now);
|
nuclear@1
|
637 ct.tm_isdst = nowtime.tm_isdst;
|
nuclear@1
|
638 sscanf_s(caltime_str, "%d-%d-%d %d:%d:%d",
|
nuclear@1
|
639 &ct.tm_year, &ct.tm_mon, &ct.tm_mday,
|
nuclear@1
|
640 &ct.tm_hour, &ct.tm_min, &ct.tm_sec);
|
nuclear@1
|
641 #else
|
nuclear@1
|
642 struct tm* nowtime = localtime(&now);
|
nuclear@1
|
643 ct.tm_isdst = nowtime->tm_isdst;
|
nuclear@1
|
644 sscanf(caltime_str, "%d-%d-%d %d:%d:%d",
|
nuclear@1
|
645 &ct.tm_year, &ct.tm_mon, &ct.tm_mday,
|
nuclear@1
|
646 &ct.tm_hour, &ct.tm_min, &ct.tm_sec);
|
nuclear@1
|
647 #endif
|
nuclear@1
|
648 ct.tm_year -= 1900;
|
nuclear@1
|
649 ct.tm_mon--;
|
nuclear@1
|
650 calibration_time = mktime(&ct);
|
nuclear@1
|
651 }
|
nuclear@1
|
652
|
nuclear@1
|
653 // parse the calibration matrix
|
nuclear@1
|
654 JSON* cal = calibration->GetItemByName("Calibration");
|
nuclear@1
|
655 if (cal)
|
nuclear@1
|
656 {
|
nuclear@1
|
657 const char* data_str = cal->Value.ToCStr();
|
nuclear@1
|
658 Matrix4f calmat;
|
nuclear@1
|
659 for (int r=0; r<4; r++)
|
nuclear@1
|
660 {
|
nuclear@1
|
661 for (int c=0; c<4; c++)
|
nuclear@1
|
662 {
|
nuclear@1
|
663 calmat.M[r][c] = (float)atof(data_str);
|
nuclear@1
|
664 while (data_str && *data_str != ' ')
|
nuclear@1
|
665 data_str++;
|
nuclear@1
|
666
|
nuclear@1
|
667 if (data_str)
|
nuclear@1
|
668 data_str++;
|
nuclear@1
|
669 }
|
nuclear@1
|
670 }
|
nuclear@1
|
671
|
nuclear@1
|
672 SetMagCalibration(calmat);
|
nuclear@1
|
673 MagCalibrationTime = calibration_time;
|
nuclear@1
|
674 EnableYawCorrection = autoEnableCorrection;
|
nuclear@1
|
675
|
nuclear@1
|
676 return true;
|
nuclear@1
|
677 }
|
nuclear@1
|
678 }
|
nuclear@1
|
679 }
|
nuclear@1
|
680 item = device->GetNextItem(item);
|
nuclear@1
|
681 }
|
nuclear@1
|
682
|
nuclear@1
|
683 break;
|
nuclear@1
|
684 }
|
nuclear@1
|
685 }
|
nuclear@1
|
686
|
nuclear@1
|
687 device = root->GetNextItem(device);
|
nuclear@1
|
688 }
|
nuclear@1
|
689
|
nuclear@1
|
690 return false;
|
nuclear@1
|
691 }
|
nuclear@1
|
692
|
nuclear@1
|
693
|
nuclear@1
|
694
|
nuclear@1
|
695 } // namespace OVR
|
nuclear@1
|
696
|