Index: content/renderer/device_sensors/device_orientation_util.cc |
diff --git a/content/renderer/device_sensors/device_orientation_util.cc b/content/renderer/device_sensors/device_orientation_util.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..15f27bfa8e9ed88a11fa6ac29fe58d91544b4e19 |
--- /dev/null |
+++ b/content/renderer/device_sensors/device_orientation_util.cc |
@@ -0,0 +1,254 @@ |
+// Copyright 2017 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#define _USE_MATH_DEFINES // For VC++ to get M_PI. This has to be first. |
+ |
+#include "content/renderer/device_sensors/device_orientation_util.h" |
+ |
+#include <cmath> |
+#include <vector> |
+ |
+#include "base/logging.h" |
+#include "third_party/WebKit/public/platform/modules/device_orientation/WebDeviceOrientationData.h" |
+ |
+namespace { |
+ |
+bool IsAngleDifferent(bool has_angle1, |
+ double angle1, |
+ bool has_angle2, |
+ double angle2) { |
+ if (has_angle1 != has_angle2) |
+ return true; |
+ return (has_angle1 && |
+ std::fabs(angle1 - angle2) >= content::kOrientationThreshold); |
+} |
+ |
+// Helper function to convert a quaternion to a rotation matrix. x, y, z, w |
+// are values of a quaternion representing the orientation of the device in |
+// 3D space. Returns a 9 element rotation matrix: |
+// r[ 0] r[ 1] r[ 2] |
+// r[ 3] r[ 4] r[ 5] |
+// r[ 6] r[ 7] r[ 8] |
+std::vector<double> ComputeRotationMatrixFromQuaternion(double x, |
+ double y, |
+ double z, |
+ double w) { |
+ std::vector<double> r(9); |
+ |
+ double sq_x = 2 * x * x; |
+ double sq_y = 2 * y * y; |
+ double sq_z = 2 * z * z; |
+ double x_y = 2 * x * y; |
+ double z_w = 2 * z * w; |
+ double x_z = 2 * x * z; |
+ double y_w = 2 * y * w; |
+ double y_z = 2 * y * z; |
+ double x_w = 2 * x * w; |
+ |
+ r[0] = 1 - sq_y - sq_z; |
+ r[1] = x_y - z_w; |
+ r[2] = x_z + y_w; |
+ r[3] = x_y + z_w; |
+ r[4] = 1 - sq_x - sq_z; |
+ r[5] = y_z - x_w; |
+ r[6] = x_z - y_w; |
+ r[7] = y_z + x_w; |
+ r[8] = 1 - sq_x - sq_y; |
+ |
+ return r; |
+} |
+ |
+// Helper function to compute the rotation matrix using gravity and geomagnetic |
+// data. Returns a 9 element rotation matrix: |
+// r[ 0] r[ 1] r[ 2] |
+// r[ 3] r[ 4] r[ 5] |
+// r[ 6] r[ 7] r[ 8] |
+// r is meaningful only when the device is not free-falling and it is not close |
+// to the magnetic north. If the device is accelerating, or placed into a |
+// strong magnetic field, the returned matricx may be inaccurate. |
+// |
+// Free fall is defined as condition when the magnitude of the gravity is less |
+// than 1/10 of the nominal value. |
+bool ComputeRotationMatrixFromGravityAndGeomagnetic(double gravity_x, |
+ double gravity_y, |
+ double gravity_z, |
+ double geomagnetic_x, |
+ double geomagnetic_y, |
+ double geomagnetic_z, |
+ std::vector<double>* r) { |
+ double gravity_squared = |
+ (gravity_x * gravity_x + gravity_y * gravity_y + gravity_z * gravity_z); |
+ double g = 9.81; |
+ double free_fall_gravity_squared = 0.01 * g * g; |
+ if (gravity_squared < free_fall_gravity_squared) { |
+ // gravity less than 10% of normal value |
+ return false; |
+ } |
+ |
+ double hx = geomagnetic_y * gravity_z - geomagnetic_z * gravity_y; |
+ double hy = geomagnetic_z * gravity_x - geomagnetic_x * gravity_z; |
+ double hz = geomagnetic_x * gravity_y - geomagnetic_y * gravity_x; |
+ double norm_h = std::sqrt(hx * hx + hy * hy + hz * hz); |
+ if (norm_h < 0.1) { |
+ // device is close to free fall, or close to magnetic north pole. |
+ // Typical values are > 100. |
+ return false; |
+ } |
+ |
+ double inv_h = 1.0 / norm_h; |
+ hx *= inv_h; |
+ hy *= inv_h; |
+ hz *= inv_h; |
+ double inv_gravity = 1.0 / std::sqrt(gravity_squared); |
+ gravity_x *= inv_gravity; |
+ gravity_y *= inv_gravity; |
+ gravity_z *= inv_gravity; |
+ double mx = gravity_y * hz - gravity_z * hy; |
+ double my = gravity_z * hx - gravity_x * hz; |
+ double mz = gravity_x * hy - gravity_y * hx; |
+ |
+ r->resize(9); |
+ |
+ (*r)[0] = hx; |
+ (*r)[1] = hy; |
+ (*r)[2] = hz; |
+ (*r)[3] = mx; |
+ (*r)[4] = my; |
+ (*r)[5] = mz; |
+ (*r)[6] = gravity_x; |
+ (*r)[7] = gravity_y; |
+ (*r)[8] = gravity_z; |
+ |
+ return true; |
+} |
+ |
+// Returns orientation angles from a rotation matrix, such that the angles are |
+// according to spec http://dev.w3.org/geo/api/spec-source-orientation.html}. |
+// |
+// It is assumed the rotation matrix transforms a 3D column vector from device |
+// coordinate system to the world's coordinate system. |
+// |
+// In particular we compute the decomposition of a given rotation matrix r such |
+// that |
+// r = rz(alpha) * rx(beta) * ry(gamma) |
+// where rz, rx and ry are rotation matrices around z, x and y axes in the world |
+// coordinate reference frame respectively. The reference frame consists of |
+// three orthogonal axes x, y, z where x points East, y points north and z |
+// points upwards perpendicular to the ground plane. The computed angles alpha, |
+// beta and gamma are in degrees and clockwise-positive when viewed along the |
+// positive direction of the corresponding axis. Except for the special case |
+// when the beta angle is +-pi/2 these angles uniquely define the orientation of |
+// a mobile device in 3D space. The alpha-beta-gamma representation resembles |
+// the yaw-pitch-roll convention used in vehicle dynamics, however it does not |
+// exactly match it. One of the differences is that the 'pitch' angle beta is |
+// allowed to be within [-pi, pi). A mobile device with pitch angle greater than |
+// pi/2 could correspond to a user lying down and looking upward at the screen. |
+// |
+// r is a 9 element rotation matrix: |
+// r[ 0] r[ 1] r[ 2] |
+// r[ 3] r[ 4] r[ 5] |
+// r[ 6] r[ 7] r[ 8] |
+// |
+// alpha: rotation around the z axis, in [0, 2*pi) |
+// beta: rotation around the x axis, in [-pi, pi) |
+// gamma: rotation around the y axis, in [-pi/2, pi/2) |
+void ComputeDeviceOrientationFromRotationMatrix(const std::vector<double>& r, |
+ double* alpha, |
+ double* beta, |
+ double* gamma) { |
+ DCHECK_EQ(9u, r.size()); |
+ |
+ if (r[8] > 0) { // cos(beta) > 0 |
+ *alpha = std::atan2(-r[1], r[4]); |
+ *beta = std::asin(r[7]); // beta (-pi/2, pi/2) |
+ *gamma = std::atan2(-r[6], r[8]); // gamma (-pi/2, pi/2) |
+ } else if (r[8] < 0) { // cos(beta) < 0 |
+ *alpha = std::atan2(r[1], -r[4]); |
+ *beta = -std::asin(r[7]); |
+ *beta += (*beta >= 0) ? -M_PI : M_PI; // beta [-pi,-pi/2) U (pi/2,pi) |
+ *gamma = std::atan2(r[6], -r[8]); // gamma (-pi/2, pi/2) |
+ } else { // r[8] == 0 |
+ if (r[6] > 0) { // cos(gamma) == 0, cos(beta) > 0 |
+ *alpha = std::atan2(-r[1], r[4]); |
+ *beta = std::asin(r[7]); // beta [-pi/2, pi/2] |
+ *gamma = -M_PI / 2; // gamma = -pi/2 |
+ } else if (r[6] < 0) { // cos(gamma) == 0, cos(beta) < 0 |
+ *alpha = std::atan2(r[1], -r[4]); |
+ *beta = -std::asin(r[7]); |
+ *beta += (*beta >= 0) ? -M_PI : M_PI; // beta [-pi,-pi/2) U (pi/2,pi) |
+ *gamma = -M_PI / 2; // gamma = -pi/2 |
+ } else { // r[6] == 0, cos(beta) == 0 |
+ // gimbal lock discontinuity |
+ *alpha = std::atan2(r[3], r[0]); |
+ *beta = (r[7] > 0) ? M_PI / 2 : -M_PI / 2; // beta = +-pi/2 |
+ *gamma = 0; // gamma = 0 |
+ } |
+ } |
+ |
+ // alpha is in [-pi, pi], make sure it is in [0, 2*pi). |
+ if (*alpha < 0) |
+ *alpha += 2 * M_PI; // alpha [0, 2*pi) |
+} |
+ |
+double RadiansToDegrees(double radians) { |
+ return (180.0 * radians) / M_PI; |
+} |
+ |
+} // namespace |
+ |
+namespace content { |
+ |
+bool IsSignificantlyDifferent(const blink::WebDeviceOrientationData& data1, |
+ const blink::WebDeviceOrientationData& data2) { |
+ return IsAngleDifferent(data1.has_alpha, data1.alpha, data2.has_alpha, |
+ data2.alpha) || |
+ IsAngleDifferent(data1.has_beta, data1.beta, data2.has_beta, |
+ data2.beta) || |
+ IsAngleDifferent(data1.has_gamma, data1.gamma, data2.has_gamma, |
+ data2.gamma); |
+} |
+ |
+void ComputeDeviceOrientationFromQuaternion(double x, |
+ double y, |
+ double z, |
+ double w, |
+ double* alpha, |
+ double* beta, |
+ double* gamma) { |
+ std::vector<double> rotation_matrix = |
+ ComputeRotationMatrixFromQuaternion(x, y, z, w); |
+ double alpha_radians, beta_radians, gamma_radians; |
+ ComputeDeviceOrientationFromRotationMatrix(rotation_matrix, &alpha_radians, |
+ &beta_radians, &gamma_radians); |
+ *alpha = RadiansToDegrees(alpha_radians); |
+ *beta = RadiansToDegrees(beta_radians); |
+ *gamma = RadiansToDegrees(gamma_radians); |
+} |
+ |
+bool ComputeDeviceOrientationFromGravityAndGeomagnetic(double gravity_x, |
+ double gravity_y, |
+ double gravity_z, |
+ double geomagnetic_x, |
+ double geomagnetic_y, |
+ double geomagnetic_z, |
+ double* alpha, |
+ double* beta, |
+ double* gamma) { |
+ std::vector<double> rotation_matrix; |
+ if (!ComputeRotationMatrixFromGravityAndGeomagnetic( |
+ gravity_x, gravity_y, gravity_z, geomagnetic_x, geomagnetic_y, |
+ geomagnetic_z, &rotation_matrix)) { |
+ return false; |
+ } |
+ |
+ double alpha_radians, beta_radians, gamma_radians; |
+ ComputeDeviceOrientationFromRotationMatrix(rotation_matrix, &alpha_radians, |
+ &beta_radians, &gamma_radians); |
+ *alpha = RadiansToDegrees(alpha_radians); |
+ *beta = RadiansToDegrees(beta_radians); |
+ *gamma = RadiansToDegrees(gamma_radians); |
+ return true; |
+} |
+ |
+} // namespace content |