使用四元数的设备方向

时间:2019-06-26 09:25:06

标签: javascript android ios mobile sensors

我已经编写了一个JS SDK,可以监听移动设备的旋转,并提供3个输入:

α:角度范围可以在0到360度之间
β:-180至180度之间的角度
γ:-90至90度之间的角度

Documentation for device rotation

我尝试使用欧拉角确定设备方向,但是遇到gimbal lock effect,当设备指向上方时,计算会爆炸。这使我使用Quaternion,而不受万向节锁定效应的影响。

我发现this js library将α,β和γ转换为四元数,因此对于以下值:

α:81.7324
β:74.8036
γ:-84.3221

我得到此四元数用于 ZXY 订单:

w :0.7120695154301472
x :0.6893688637611577
y :-0.10864439143062626
z :0.07696733776346154

代码:

var rad = Math.PI / 180;
window.addEventListener("deviceorientation", function(ev) {

  // Update the rotation object
  var q = Quaternion.fromEuler(ev.alpha * rad, ev.beta * rad, ev.gamma * rad, 'ZXY');

  // Set the CSS style to the element you want to rotate
  elm.style.transform = "matrix3d(" + q.conjugate().toMatrix4() + ")";

}, true);

使用从四元数派生的4d CSS矩阵可视化设备方向反映了正确的设备方向(DEMO, use mobile):
enter image description here


错误:使用Euler Angels和开发者工具(DEMO, use mobile)进行可视化:
enter image description here


如果设备处于以下方向之一,我想编写一个获取α,β和γ并输出的方法:

  • 肖像
  • 人像倒挂
  • 左景观
  • 右景观
  • 显示
  • 向下显示

将每个方向定义为围绕相关轴的±45°范围。

我应该采取什么方法?

1 个答案:

答案 0 :(得分:1)

鉴于您已经成功将欧拉角转换为单位四元数,这是一种确定设备方向的简单方法:

  1. 获取一个笔直向上(即沿+ z 轴)指向的世界空间矢量,并使用四元数(或其共轭)将其旋转为设备坐标。 (请注意,您也可以直接使用欧拉角,或使用旋转矩阵,或使用可用于变换矢量的设备旋转的任何其他表示形式进行此操作。)

  2. 取变换后的向量,找到绝对值最大的分量。这将告诉您设备的哪个轴指向最接近垂直的方向,并且组件值的符号告诉您它是向上还是向下。

尤其是:

  • 如果设备 x 轴最垂直,则设备处于横向方向;
  • 如果设备 y 轴最垂直,则设备处于纵向方向;
  • 如果设备的 z 轴最垂直,则设备的屏幕向上或向下。

这是一个简单的JS演示,至少应在Chrome上运行-否则,除了设备定向API似乎根本不能在Stack Snippets中运行。 :(有关实时演示,请尝试this CodePen instead

const orientations = [
  ['landscape left', 'landscape right'],  // device x axis points up/down
  ['portrait', 'portrait upside down'],   // device y axis points up/down
  ['display up', 'display down'],         // device z axis points up/down
];

const rad = Math.PI / 180;

function onOrientationChange (ev) {
  const q = Quaternion.fromEuler(ev.alpha * rad, ev.beta * rad, ev.gamma * rad, 'ZXY');

  // transform an upward-pointing vector to device coordinates
  const vec = q.conjugate().rotateVector([0, 0, 1]);

  // find the axis with the largest absolute value
  const [value, axis] = vec.reduce((acc, cur, idx) => (Math.abs(cur) < Math.abs(acc[0]) ? acc : [cur, idx]), [0, 0]);

  const orientation = orientations[axis][1 * (value < 0)];

  document.querySelector('#angles').textContent = `alpha = ${ev.alpha.toFixed(1)}°, beta = ${ev.beta.toFixed(1)}°, gamma = ${ev.gamma.toFixed(1)}°`;
  document.querySelector('#vec').textContent = `vec = ${vec.map(a => a.toFixed(3))}, dominant axis = ${axis}, value = ${value.toFixed(3)}`;
  document.querySelector('#orientation').textContent = `orientation = ${orientation}`;
}

onOrientationChange({ alpha: 0, beta: 0, gamma: 0 });
window.addEventListener("deviceorientation", onOrientationChange, true);
<script src="https://cdn.jsdelivr.net/npm/quaternion@1.1.0/quaternion.min.js"></script>
<div id="angles"></div>
<div id="vec"></div>
<div id="orientation"></div>

请注意,在设备方向API提供的欧拉角的符号和范围内,浏览器之间存在apparently不一致,可能导致在其他浏览器上计算出错误的符号。您可能需要进行一些浏览器嗅探才能解决此问题,或者使用gyronorm.js之类的包装器库。