html5 - Get device orientation rotation in relative coordinate

前端 未结 1 1805
清酒与你
清酒与你 2021-01-12 23:01

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 defin

1条回答
  •  一整个雨季
    2021-01-12 23:33

    All right. First, a simple explanation of the device orientation input:

    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:

    • rotate around Z by alpha degrees, which transforms (X, Y, Z) to (X', Y', Z') with Z' = Z
    • rotate around X' by beta degrees, which transforms (X', Y', Z') to (X'', Y'', Z'') with X'' = X'
    • rotate around Y'' by gamma degrees, which transforms (X'', Y'', Z'') to (x, y, z) with y = Y''

    (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  ]
    

    where A, B, C are short for alpha, beta, gamma and s, c for sin, cos.

    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')

    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)

    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;
    

    by

    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:

      • keep your phone horizontal and rotate by 90 degrees clockwise. (this is neither a left-right nor a top-bottom rotation)
      • keep your phone in landscape mode and rotate by 90 toward you. (this is neither a 90 degrees left-right rotation)
      • keep your phone facing you and rotate by 90 so that it's up. (this is neither a 90 degrees left-right rotation)
    • method 2:

      • rotate the phone by 90 degrees so that it faces you and is vertical (this is a 90 degrees top-bottom rotation)

    0 讨论(0)
提交回复
热议问题