html5 - 获取相对坐标中的设备方向旋转
html5 - Get device orientation rotation in relative coordinate
我正在尝试获取沿左右轴和上下轴的两个 deviceorientation
事件之间的方向变化,这些轴通常定义为 phone x
和 y
轴 (https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Orientation_and_motion_data_explained)
即在 t1
和 t2
之间,那些 phone 轴从 (x1, y1)
移动到 (x2, y2)
,它想要得到 (angle(x2-x1), angle(y1-y2))
.
当设备处于纵向模式(与横向模式相反)时,这些轴似乎对应于 beta
和 gamma
。但是当 phone 是垂直的(底部朝向地面)时, gamma
值变得非常不稳定,并从 90 度跳到 -90 度(同时,alpha 跳了 180 度)你很容易看出 here on your phone
我想避免这种情况,并且还希望获得 360 度范围内的值。这是我目前所拥有的:
// assuming portrait mode
var beta0, gamma0;
window.addEventListener('deviceorientation', function(orientation) {
if (typeof beta0 === 'undefined') {
beta0 = beta;
gamma0 = gamma;
}
console.log('user has moved to the left by', gamma - gamma0, ' and to the top by', beta - beta0);
});
当设备大部分处于水平状态时可以正常工作,而当设备处于垂直状态时则完全不行
好的。先简单解释下设备方向输入:
绝对坐标系,(X, Y, Z)
X
是东,Y
是北,Z
是上。设备相对坐标系,(x, y, z)
是这样的,x
在右,y
在上,z
在上。然后方向角 (alpha, beta, gamma)
是描述三个简单旋转连续的角度,这些旋转将 (X, Y, Z)
变为 (x, y, z)
,如下所示:
- 围绕
Z
旋转 alpha
度,从而将 (X, Y, Z)
转换为 (X', Y', Z')
Z'
= Z
- 围绕
X'
旋转 beta
度,将 (X', Y', Z')
转换为 (X'', Y'', Z'')
X''
= X'
- 围绕
Y''
旋转 gamma
度,将 (X'', Y'', Z'')
转换为 (x, y, z)
,其中 y
= Y''
(它们被称为 Z-X'-Y''
类型的固有 Tait-Bryan 角)
现在我们可以通过组合简单的旋转矩阵来得到相应的旋转矩阵,每个旋转矩阵分别对应三个旋转中的一个。
[ cC 0 sC ] [ 1 0 0 ] [ cA -sA 0 ]
R(A, B, C) = Ry(C)*Rx(B)*Rz(A) = | 0 1 0 |*| 0 cB -sB |*[ sA cA 0 ]
[ -sC 0 cC ] [ 0 sB cB ] [ 0 0 1 ]
其中 A, B, C
是 alpha, beta, gamma
的缩写,s, c
是 sin, cos
的缩写。
现在,我们感兴趣的是两个位置 (x, y, z)
和 (x', y', z')
对应方向 (A, B, C)
和 (A', B', C')
(x, y, z)
中 (x', y', z')
的坐标由 R(A', B', C') * R(A, B, C)^-1 = R(A', B', C') * R(A, B, C)^T
给出,因为逆矩阵是正交(旋转)矩阵的转置。最后,如果 z' = p*x + q*y + r*z
,这些旋转的角度是围绕左右轴的 p
和围绕自上而下的轴的 q
(这对于小角度是正确的,它假设频繁的方向更新,否则 asin(p)
和 asin(r)
更接近事实)
所以这里有一些javascript来获得旋转矩阵:
/*
* gl-matrix is a nice library that handles rotation stuff efficiently
* The 3x3 matrix is a 9 element array
* such that indexes 0-2 correspond to the first column, 3-5 to the second column and 6-8 to the third
*/
import {mat3} from 'gl-matrix';
let _x, _y, _z;
let cX, cY, cZ, sX, sY, sZ;
/*
* return the rotation matrix corresponding to the orientation angles
*/
const fromOrientation = function(out, alpha, beta, gamma) {
_z = alpha;
_x = beta;
_y = gamma;
cX = Math.cos( _x );
cY = Math.cos( _y );
cZ = Math.cos( _z );
sX = Math.sin( _x );
sY = Math.sin( _y );
sZ = Math.sin( _z );
out[0] = cZ * cY + sZ * sX * sY, // row 1, col 1
out[1] = cX * sZ, // row 2, col 1
out[2] = - cZ * sY + sZ * sX * cY , // row 3, col 1
out[3] = - cY * sZ + cZ * sX * sY, // row 1, col 2
out[4] = cZ * cX, // row 2, col 2
out[5] = sZ * sY + cZ * cY * sX, // row 3, col 2
out[6] = cX * sY, // row 1, col 3
out[7] = - sX, // row 2, col 3
out[8] = cX * cY // row 3, col 3
};
现在我们得到 angular 增量:
const deg2rad = Math.PI / 180; // Degree-to-Radian conversion
let currentRotMat, previousRotMat, inverseMat, relativeRotationDelta,
totalRightAngularMovement=0, totalTopAngularMovement=0;
window.addEventListener('deviceorientation', ({alpha, beta, gamma}) => {
// init values if necessary
if (!previousRotMat) {
previousRotMat = mat3.create();
currentRotMat = mat3.create();
relativeRotationDelta = mat3.create();
fromOrientation(currentRotMat, alpha * deg2rad, beta * deg2rad, gamma * deg2rad);
}
// save last orientation
mat3.copy(previousRotMat, currentRotMat);
// get rotation in the previous orientation coordinate
fromOrientation(currentRotMat, alpha * deg2rad, beta * deg2rad, gamma * deg2rad);
mat3.transpose(inverseMat, previousRotMat); // for rotation matrix, inverse is transpose
mat3.multiply(relativeRotationDelta, currentRotMat, inverseMat);
// add the angular deltas to the cummulative rotation
totalRightAngularMovement += Math.asin(relativeRotationDelta[6]) / deg2rad;
totalTopAngularMovement += Math.asin(relativeRotationDelta[7]) / deg2rad;
}
最后,为了考虑屏幕方向,我们必须替换
_z = alpha;
_x = beta;
_y = gamma;
来自
const screen = window.screen;
const getScreenOrientation = () => {
const oriented = screen && (screen.orientation || screen.mozOrientation);
if (oriented) switch (oriented.type || oriented) {
case 'landscape-primary':
return 90;
case 'landscape-secondary':
return -90;
case 'portrait-secondary':
return 180;
case 'portrait-primary':
return 0;
}
return window.orientation|0; // defaults to zero if orientation is unsupported
};
const screenOrientation = getScreenOrientation();
_z = alpha;
if (screenOrientation === 90) {
_x = - gamma;
_y = beta;
}
else if (screenOrientation === -90) {
_x = gamma;
_y = - beta;
}
else if (screenOrientation === 180) {
_x = - beta;
_y = - gamma;
}
else if (screenOrientation === 0) {
_x = beta;
_y = gamma;
}
请注意,累积的左右角度和上下角度将取决于用户选择的路径,不能直接从设备方向推断,但必须通过运动进行跟踪。您可以通过不同的动作到达相同的位置:
方法一:
- 保持 phone 水平并顺时针旋转 90 度。 (这既不是左右旋转也不是上下旋转)
- 让你的 phone 保持横向模式并向你旋转 90 度。 (这既不是90度左右旋转)
- 让你的 phone 面向你并旋转 90 度使其向上。 (这既不是90度左右旋转)
方法二:
- 将 phone 旋转 90 度,使其面向您并垂直(这是 90 度上下旋转)
我正在尝试获取沿左右轴和上下轴的两个 deviceorientation
事件之间的方向变化,这些轴通常定义为 phone x
和 y
轴 (https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Orientation_and_motion_data_explained)
即在 t1
和 t2
之间,那些 phone 轴从 (x1, y1)
移动到 (x2, y2)
,它想要得到 (angle(x2-x1), angle(y1-y2))
.
当设备处于纵向模式(与横向模式相反)时,这些轴似乎对应于 beta
和 gamma
。但是当 phone 是垂直的(底部朝向地面)时, gamma
值变得非常不稳定,并从 90 度跳到 -90 度(同时,alpha 跳了 180 度)你很容易看出 here on your phone
我想避免这种情况,并且还希望获得 360 度范围内的值。这是我目前所拥有的:
// assuming portrait mode
var beta0, gamma0;
window.addEventListener('deviceorientation', function(orientation) {
if (typeof beta0 === 'undefined') {
beta0 = beta;
gamma0 = gamma;
}
console.log('user has moved to the left by', gamma - gamma0, ' and to the top by', beta - beta0);
});
当设备大部分处于水平状态时可以正常工作,而当设备处于垂直状态时则完全不行
好的。先简单解释下设备方向输入:
绝对坐标系,(X, Y, Z)
X
是东,Y
是北,Z
是上。设备相对坐标系,(x, y, z)
是这样的,x
在右,y
在上,z
在上。然后方向角 (alpha, beta, gamma)
是描述三个简单旋转连续的角度,这些旋转将 (X, Y, Z)
变为 (x, y, z)
,如下所示:
- 围绕
Z
旋转alpha
度,从而将(X, Y, Z)
转换为(X', Y', Z')
Z'
=Z
- 围绕
X'
旋转beta
度,将(X', Y', Z')
转换为(X'', Y'', Z'')
X''
=X'
- 围绕
Y''
旋转gamma
度,将(X'', Y'', Z'')
转换为(x, y, z)
,其中y
=Y''
(它们被称为 Z-X'-Y''
类型的固有 Tait-Bryan 角)
现在我们可以通过组合简单的旋转矩阵来得到相应的旋转矩阵,每个旋转矩阵分别对应三个旋转中的一个。
[ cC 0 sC ] [ 1 0 0 ] [ cA -sA 0 ]
R(A, B, C) = Ry(C)*Rx(B)*Rz(A) = | 0 1 0 |*| 0 cB -sB |*[ sA cA 0 ]
[ -sC 0 cC ] [ 0 sB cB ] [ 0 0 1 ]
其中 A, B, C
是 alpha, beta, gamma
的缩写,s, c
是 sin, cos
的缩写。
现在,我们感兴趣的是两个位置 (x, y, z)
和 (x', y', z')
对应方向 (A, B, C)
和 (A', B', C')
(x, y, z)
中 (x', y', z')
的坐标由 R(A', B', C') * R(A, B, C)^-1 = R(A', B', C') * R(A, B, C)^T
给出,因为逆矩阵是正交(旋转)矩阵的转置。最后,如果 z' = p*x + q*y + r*z
,这些旋转的角度是围绕左右轴的 p
和围绕自上而下的轴的 q
(这对于小角度是正确的,它假设频繁的方向更新,否则 asin(p)
和 asin(r)
更接近事实)
所以这里有一些javascript来获得旋转矩阵:
/*
* gl-matrix is a nice library that handles rotation stuff efficiently
* The 3x3 matrix is a 9 element array
* such that indexes 0-2 correspond to the first column, 3-5 to the second column and 6-8 to the third
*/
import {mat3} from 'gl-matrix';
let _x, _y, _z;
let cX, cY, cZ, sX, sY, sZ;
/*
* return the rotation matrix corresponding to the orientation angles
*/
const fromOrientation = function(out, alpha, beta, gamma) {
_z = alpha;
_x = beta;
_y = gamma;
cX = Math.cos( _x );
cY = Math.cos( _y );
cZ = Math.cos( _z );
sX = Math.sin( _x );
sY = Math.sin( _y );
sZ = Math.sin( _z );
out[0] = cZ * cY + sZ * sX * sY, // row 1, col 1
out[1] = cX * sZ, // row 2, col 1
out[2] = - cZ * sY + sZ * sX * cY , // row 3, col 1
out[3] = - cY * sZ + cZ * sX * sY, // row 1, col 2
out[4] = cZ * cX, // row 2, col 2
out[5] = sZ * sY + cZ * cY * sX, // row 3, col 2
out[6] = cX * sY, // row 1, col 3
out[7] = - sX, // row 2, col 3
out[8] = cX * cY // row 3, col 3
};
现在我们得到 angular 增量:
const deg2rad = Math.PI / 180; // Degree-to-Radian conversion
let currentRotMat, previousRotMat, inverseMat, relativeRotationDelta,
totalRightAngularMovement=0, totalTopAngularMovement=0;
window.addEventListener('deviceorientation', ({alpha, beta, gamma}) => {
// init values if necessary
if (!previousRotMat) {
previousRotMat = mat3.create();
currentRotMat = mat3.create();
relativeRotationDelta = mat3.create();
fromOrientation(currentRotMat, alpha * deg2rad, beta * deg2rad, gamma * deg2rad);
}
// save last orientation
mat3.copy(previousRotMat, currentRotMat);
// get rotation in the previous orientation coordinate
fromOrientation(currentRotMat, alpha * deg2rad, beta * deg2rad, gamma * deg2rad);
mat3.transpose(inverseMat, previousRotMat); // for rotation matrix, inverse is transpose
mat3.multiply(relativeRotationDelta, currentRotMat, inverseMat);
// add the angular deltas to the cummulative rotation
totalRightAngularMovement += Math.asin(relativeRotationDelta[6]) / deg2rad;
totalTopAngularMovement += Math.asin(relativeRotationDelta[7]) / deg2rad;
}
最后,为了考虑屏幕方向,我们必须替换
_z = alpha;
_x = beta;
_y = gamma;
来自
const screen = window.screen;
const getScreenOrientation = () => {
const oriented = screen && (screen.orientation || screen.mozOrientation);
if (oriented) switch (oriented.type || oriented) {
case 'landscape-primary':
return 90;
case 'landscape-secondary':
return -90;
case 'portrait-secondary':
return 180;
case 'portrait-primary':
return 0;
}
return window.orientation|0; // defaults to zero if orientation is unsupported
};
const screenOrientation = getScreenOrientation();
_z = alpha;
if (screenOrientation === 90) {
_x = - gamma;
_y = beta;
}
else if (screenOrientation === -90) {
_x = gamma;
_y = - beta;
}
else if (screenOrientation === 180) {
_x = - beta;
_y = - gamma;
}
else if (screenOrientation === 0) {
_x = beta;
_y = gamma;
}
请注意,累积的左右角度和上下角度将取决于用户选择的路径,不能直接从设备方向推断,但必须通过运动进行跟踪。您可以通过不同的动作到达相同的位置:
方法一:
- 保持 phone 水平并顺时针旋转 90 度。 (这既不是左右旋转也不是上下旋转)
- 让你的 phone 保持横向模式并向你旋转 90 度。 (这既不是90度左右旋转)
- 让你的 phone 面向你并旋转 90 度使其向上。 (这既不是90度左右旋转)
方法二:
- 将 phone 旋转 90 度,使其面向您并垂直(这是 90 度上下旋转)