使用四元数实现设备定向
我写了一个监听移动设备旋转的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):
我想编写一个方法,用于在设备处于以下方向之一时获取α,β和γ并输出:
- 肖像
- 人像颠倒
- 左侧横向
- 横向向右
- 向上显示
- 向下显示
我应该采取什么方法?
解决方案
假设您已经成功地将欧拉角转换为单位四元数,下面是确定设备方向的简单方法:
将世界空间向量直接指向(即沿+z轴),并使用四元数(或其共轭)将其旋转到设备坐标。(请注意,您也可以直接使用欧拉角,或使用旋转矩阵,或使用可用于变换矢量的设备旋转的任何其他表示法。)
取变换后的向量,找出绝对值最大的分量。这将告诉您设备的哪个轴指向最接近垂直,而组件值的符号告诉您它指向的是向上还是向下。
特别是:
- 如果设备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的包装库。
相关文章