使用四元数实现设备定向

2022-08-31 00:00:00 sensors javascript android mobile ios

我写了一个监听移动设备旋转的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 CS矩阵可视化设备方向反映了正确的设备方向(DEMO, use mobile):


错误使用Euler Angels和开发工具可视化(DEMO, use mobile):


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

  • 肖像
  • 人像颠倒
  • 左侧横向
  • 横向向右
  • 向上显示
  • 向下显示
将每个方向定义为围绕相关轴的+-45°范围。

我应该采取什么方法?


解决方案

假设您已经成功地将欧拉角转换为单位四元数,下面是确定设备方向的简单方法:

  1. 将世界空间向量直接指向(即沿+z轴),并使用四元数(或其共轭)将其旋转到设备坐标。(请注意,您也可以直接使用欧拉角,或使用旋转矩阵,或使用可用于变换矢量的设备旋转的任何其他表示法。)

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

特别是:

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

这里有一个简单的JS演示,它至少应该在Chrome上工作--或者它会工作,除了设备定向API似乎根本不能在Stack代码片断中工作。:(有关现场演示,请尝试this CodePen instead。

数据-lang="js"数据-隐藏="假"数据-控制台="真"数据-巴贝尔="假">
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的包装库。

相关文章