html5 - 以相对坐标获取设备方向旋转
我试图在两个 deviceorientation
事件之间沿左右轴和上下轴改变方向,这些轴通常定义为电话 x
和 y
轴(https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Orientation_and_motion_data_explained)
I'm trying to get the change in orientation between two deviceorientation
events along the left-right axis, and top-bottom axis, those axis being usually defined as the phone x
and y
axis (https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Orientation_and_motion_data_explained)
即在瞬间 t1
和 t2
之间,这些音素轴从 (x1, y1)
移动到 (x2, y2)
,想得到(angle(x2-x1), angle(y1-y2))
.
ie between instants t1
and t2
where those phone axis move from (x1, y1)
to (x2, y2)
, It'd like to get (angle(x2-x1), angle(y1-y2))
.
当设备处于纵向模式(与横向模式相反)时,这些轴似乎对应于 beta
和 gamma
.但是当手机垂直(底面朝地)时,gamma
值变得极不稳定,从90度跳到-90度(同时alpha跳180度)你可以在您的手机上轻松查看此处
When the device is in portrait mode (in opposition to landscape mode), those axis seems to correspond to the beta
and gamma
. However when the phone is vertical (bottom facing the ground), the gamma
value becomes extremely instable, and jumps from 90 to -90 degrees (at the same occasion, the alpha jumps by 180 degrees) You can easily see that here on your phone
我想避免这种情况,并获得 360 范围内的值.这是我目前所拥有的:
I'd like to avoid that, and also get values in the 360 range. Here is what I have so far:
// 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);
});
当设备大部分是水平的时候可以正常工作,而当它是垂直的时候就不行了
That works ok when the device is mostly horizontal, and not at all when it is vertical
推荐答案
好的.先简单解释一下设备方向输入:
All right. First, a simple explanation of the device orientation input:
绝对坐标系,(X, Y, Z)
使得X
为东,Y
为北,Z
已启动.设备相对坐标系,(x, y, z)
使得x
是对的,y
是顶部,z
已启动.然后方向角,(alpha, beta, gamma)
是描述三个简单旋转连续的角度,这些旋转将 (X, Y, Z)
更改为 (x, y, z)
如下:
The absolute coordinate system, (X, Y, Z)
is such that X
is East, Y
is North and Z
is up. The device relative coordinate system, (x, y, z)
is such that x
is right, y
is top and z
is up. Then the orientation angles, (alpha, beta, gamma)
are the angles that describe the succession of three simple rotations that change (X, Y, Z)
to (x, y, z)
as so:
- 围绕
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) withy
=Y''
- rotate around
Z
byalpha
degrees, which transforms(X, Y, Z)
to(X', Y', Z')
withZ'
=Z
- rotate around
X'
bybeta
degrees, which transforms(X', Y', Z')
to(X'', Y'', Z'')
withX''
=X'
- rotate around
Y''
bygamma
degrees, which transforms(X'', Y'', Z'')
to(x, y, z)
withy
=Y''
(它们被称为 Z-X'-Y''
类型的固有 Tait-Bryan 角)
(they are called intrinsic Tait-Bryan angles of type Z-X'-Y''
)
现在我们可以通过组合简单的旋转矩阵得到对应的旋转矩阵,每个旋转矩阵对应三个旋转之一.
Now we can get the corresponding rotation matrix by composing simple rotation matrix that each correspond to one of the three rotations.
[ 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代码>.
where A, B, C
are short for alpha, beta, gamma
and s, c
for sin, cos
.
现在,我们感兴趣的是左右(y
轴)和自上而下(x
轴)旋转的角度在两个位置之间的增量(x, y, z)
和 (x', y', z')
对应于方向 (A, B, C)
和 <代码>(A', B', C')
Now, we are interested in the angles of the right-left (y
axis) and top-down (x
axis) rotations deltas between two positions (x, y, z)
and (x', y', z')
that correspond to the orientations (A, B, C)
and (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)
更接近说实话)
The coordinates of (x', y', z')
in term of (x, y, z)
are given by R(A', B', C') * R(A, B, C)^-1 = R(A', B', C') * R(A, B, C)^T
since the inverse is the transpose for orthogonal (rotation) matrix. Finally, if z' = p*x + q*y + r*z
, the angle of those rotations are p
around the right-left axis and q
around the top-down one (this is true for small angles, which assume frequent orientation update, else asin(p)
and asin(r)
are closer from the truth)
所以这里有一些 javascript 来获取旋转矩阵:
So here is some javascript to get the rotation matrix:
/*
* 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
};
现在我们得到了角度增量:
and now we get the angular deltas:
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;
}
最后,考虑到屏幕方向,我们必须替换
Finally, to account for screen orientation, we have to replace
_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;
}
请注意,累积的左右和上下角度取决于用户选择的路径,不能直接从设备方向推断,而是必须通过移动进行跟踪.您可以通过不同的动作到达相同的位置:
Note that the cumulative right-left and top-bottom angles will depend of the path chosen by the user, and cannot be infer directly from the device orientation but have to be tracked through the movement. You can arrive to the same position with different movements:
方法一:
method 1:
- 保持手机水平并顺时针旋转 90 度.(这既不是左右旋转也不是上下旋转)
- 让您的手机保持横向模式,然后朝您的方向旋转 90 度.(这既不是 90 度左右旋转)
- 让您的手机面向您并旋转 90 度以使其向上.(这既不是 90 度左右旋转)
方法二:
- 将手机旋转 90 度,使其面向您并且垂直(这是 90 度上下旋转)
相关文章