使用四元数的设备方向
Device orientation using Quaternion
我编写了一个监听移动设备旋转的 JS SDK,提供 3 个输入:
α : 角度范围在 0 到 360 度之间
β:-180 到 180 度之间的角度
γ:-90 到 90 度之间的角度
Documentation for device rotation
我曾尝试使用欧拉角来确定设备方向,但遇到了 gimbal lock effect, that made calculation explode when the device was pointing up. That lead me to use 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):
错误 使用 Euler Angels 和开发人员工具进行可视化 (DEMO, use mobile):
我想编写一个获取 α、β 和 γ 的方法,并在设备处于以下方向之一时输出:
- 纵向
- 人像颠倒
- 横向左侧
- 横向右
- 显示向上
- 向下显示
将每个方向定义为围绕相关轴的 +- 45° 范围。
我应该采取什么方法?
鉴于您已经设法将欧拉角转换为单位四元数,下面是确定设备方向的简单方法:
取一个world-space向量直接向上(即沿着+z轴)并使用四元数(或其共轭)旋转它进入设备坐标。 (请注意,您也可以直接使用欧拉角,或使用旋转矩阵,或使用任何其他可应用于变换向量的设备旋转表示来执行此操作。)
取变换后的向量,找到绝对值最大的分量。这将告诉您设备的哪个轴最接近垂直,组件值的符号告诉您它是向上还是向下。
特别是:
- 如果设备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>
请注意 apparently some inconsistency between browsers in the signs and ranges of the Euler angles supplied by the device orientation API, potentially causing the wrong signs to be computed on other browsers. You might need to do some browser sniffing to fix this, or use a wrapper library like gyronorm.js。
我编写了一个监听移动设备旋转的 JS SDK,提供 3 个输入:
α : 角度范围在 0 到 360 度之间
β:-180 到 180 度之间的角度
γ:-90 到 90 度之间的角度
Documentation for device rotation
我曾尝试使用欧拉角来确定设备方向,但遇到了 gimbal lock effect, that made calculation explode when the device was pointing up. That lead me to use 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):
错误 使用 Euler Angels 和开发人员工具进行可视化 (DEMO, use mobile):
我想编写一个获取 α、β 和 γ 的方法,并在设备处于以下方向之一时输出:
- 纵向
- 人像颠倒
- 横向左侧
- 横向右
- 显示向上
- 向下显示
将每个方向定义为围绕相关轴的 +- 45° 范围。
我应该采取什么方法?
鉴于您已经设法将欧拉角转换为单位四元数,下面是确定设备方向的简单方法:
取一个world-space向量直接向上(即沿着+z轴)并使用四元数(或其共轭)旋转它进入设备坐标。 (请注意,您也可以直接使用欧拉角,或使用旋转矩阵,或使用任何其他可应用于变换向量的设备旋转表示来执行此操作。)
取变换后的向量,找到绝对值最大的分量。这将告诉您设备的哪个轴最接近垂直,组件值的符号告诉您它是向上还是向下。
特别是:
- 如果设备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>
请注意 apparently some inconsistency between browsers in the signs and ranges of the Euler angles supplied by the device orientation API, potentially causing the wrong signs to be computed on other browsers. You might need to do some browser sniffing to fix this, or use a wrapper library like gyronorm.js。