我试图在沿左右轴和上下轴的两个deviceorientation事件之间改变方向,这些轴通常被定义为电话xy轴(https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Orientation_and_motion_data_explained)

即在瞬间t1t2之间,这些电话轴从(x1, y1)移到(x2, y2),它想得到(angle(x2-x1), angle(y1-y2))

当设备处于纵向模式(与横向模式相对)时,这些轴似乎对应于betagamma。但是,当电话是垂直的(底部朝向地面)时,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是East,Y是North而Z是up。设备相对坐标系统(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的缩写。

    现在,我们对与方位yx相对应的两个位置(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
    };
    

    现在我们得到了角度增量:
    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;
    }
    

    请注意,累积的左右角度和上下角度将取决于用户选择的路径,不能直接从设备方向推断出,而必须通过移动进行跟踪。您可以通过不同的 Action 到达相同的位置:
  • 方法1:
  • 保持手机水平,并顺时针旋转90度。 (这既不是左右旋转也不是上下旋转)
  • 将手机保持在横向模式,并向您旋转90度。 (这既不是左右旋转90度)
  • 保持手机面向您并旋转90度,以使其抬起。 (这既不是左右旋转90度)
  • 方法2:
  • 将手机旋转90度,使其面向您并垂直(这是上下旋转90度)
  • 10-06 13:23
    查看更多