28

Starting with...

window.addEventListener("deviceorientation", handleDeviceTilt);
function handleDeviceTilt(event){
// Here we can use event.beta, event.gamma
// Note that we don't need event.alpha because that's just the compass as we
// don't need to know which way is north to build a simple bubble-level app
// or similar apps.
}

...in this case beta and gamma values are nice and usable as long as the phone or tablet is held parallel to the ground (like resting on a table). But when the phone or tablet is held parallel to a wall, we can't get realistic values. gamma gets especially crazy when the device is held upright/vertically (i.e. portrait mode) which is the most common way of holding a mobile device.

Please observe the problem with an Android device by looking at the cube HERE

enter image description here

We need to calculate the real angles so that we can use the tilt information accurately in the app. The question is,

What would be the simplest way to calculate the real angles like realBeta and realGamma (perhaps by using Math.cos() and Math.sin() or by some other method) in order to unlock the so called gimbal lock? Can this be done without matrices and/or quaternions? My guess is "yes" because the "distortedness" in gamma is proportional in some way to beta. If quaternions are the only solution then how exactly do we get realGamma or actualGamma or fixedCorrectGamma using them?

Note 1: There is a spirit level app on PlayStore that does show the real tilt angles of the device perfectly. So there is proof that it is doable. However as of 2021 there seems to be no open source code available for such an app written in javascript.

Note 2: This issue has been mentioned here.

Note 3: Precision is necessary. The closer we get to exact angles the better. alpha can be omitted for simplicity.

WHAT IS THIS USEFUL FOR?

By solving this problem we would be able to use deviceorientation to make something similar to this, enter image description here


...or for example we could use gamma to steer a car in a vertical racing game. enter image description here


You can observe the problem in action by visiting HERE (or by finding something similar) on your mobile device. Turn on the switch that says [Show orientation angles] and watch γ (gamma) as beta approaches 90deg. Also see what happens to the Gyro-Cube when you try to hold your device in a perfectly upright position.

Final thought,

We may need some arcane mathmagics.

HolyResistance
  • 594
  • 1
  • 8
  • 26
  • 7
    Converting the angles to Quaternion could be a solution. Take a look at this similar question, https://stackoverflow.com/questions/56769428/device-orientation-using-quaternion – HardcoreGamer Sep 17 '21 at 01:18
  • 7
    You may want to remove the PS statement in your question, as it is unnecessarily confrontational. You're asking the internet to solve your problem for you without demonstrating any of your own work or elaborating on why your restrictions are in place. You may want to elaborate why some of your restrictions are included (alpha is likely required, quaternions are an obvious solution, and modernizing "outdated" libraries is always an option). The internet is not responsible for testing your solution for you, and you should always verify any answer yourself before using it in production. – rfestag Oct 16 '21 at 17:00
  • 1
    Sure, why not. Those statements were temporary anyways and were just targeting the smart people who are willing to earn some reward. Done edits and now, I think it's more compatible with the pure-research-mode. – HolyResistance Oct 16 '21 at 20:46
  • 3
    Looking back at this, it's still not quite clear what you are trying to do. You mention caring about only beta and gamma, but what is your goal? To implement a web application like the Spirit Level app you reference? Your diagram implies that you are particularly interested in holding the phone portrait mode, and detecting rotation about the axis that points at the user (assuming it is flat against a wall). In that case, only beta should matter, and the jitter of gamma would be irrelevant. Or do you also care about tilt out from the wall? – rfestag Oct 16 '21 at 21:45
  • 3
    Check this - https://codepen.io/nitnelav/pen/KxYdqB – S H A S H A N K Oct 22 '21 at 18:41
  • 1
    Thank you @SHASHANK. I checked it. It sure contains some useful code. However after testing it on my devices I think it needs some more tweaking to make it approach accurate angles when gimbal lock happens. Your input is appreciated. – HolyResistance Oct 22 '21 at 23:44
  • https://github.com/kublaj/sample-bubble-level – John Oct 28 '21 at 08:06
  • Thank you @John. I've read through the code and it looks a bit outdated and there is no live-demo page to test it. Can't tell if the magic formula that we need is actually somewhere in there. Nevertheless your input is appreciated. – HolyResistance Oct 29 '21 at 05:46
  • "*We need to calculate the real angles*" - sorry, but can you describe better what kind of "real" angle you want? What is the expected output? Also I don't understand why you refer to "realistic", "precise" and "exact" values. You can't get anything better than the input you get from the event, and I doubt that these are "unrealistic" or "wrong" values. – Bergi Nov 04 '21 at 03:55
  • Have you seen https://w3c.github.io/deviceorientation/#worked-example-2? – Bergi Nov 04 '21 at 03:56
  • @Bergi You may find on the internet that other people also call it crazy angles or insane values. You need to observe gimbal-lock in action to understand the problem that is being referred to. And yes I have seen https://w3c.github.io/deviceorientation/#worked-example-2 – HolyResistance Nov 04 '21 at 06:07
  • Is [this](https://www.dreamincode.net/forums/topic/349917-convert-from-quaternion-to-euler-angles-vector3/) what You need? – deblocker Nov 09 '21 at 15:08

2 Answers2

1

The three angles you are given is an orientation expressed as a chain of 3 rotations, in a specific order, from a specific starting point.

What you are asking for is this same orientation expressed as a different chain of rotations from a different starting point.

Converting between these representations requires linear algebra. That means using matrices.

The first step is to convert the angles to a rotation matrix (code from here):

function getRotationMatrix( alpha, beta, gamma ) {
    const degtorad = Math.PI / 180; // Degree-to-Radian conversion
    var cX = Math.cos( beta  * degtorad );
    var cY = Math.cos( gamma * degtorad );
    var cZ = Math.cos( alpha * degtorad );
    var sX = Math.sin( beta  * degtorad );
    var sY = Math.sin( gamma * degtorad );
    var sZ = Math.sin( alpha * degtorad );

    var m11 = cZ * cY - sZ * sX * sY;
    var m12 = - cX * sZ;
    var m13 = cY * sZ * sX + cZ * sY;

    var m21 = cY * sZ + cZ * sX * sY;
    var m22 = cZ * cX;
    var m23 = sZ * sY - cZ * cY * sX;

    var m31 = - cX * sY;
    var m32 = sX;
    var m33 = cX * cY;

A way to read the matrix is that it contains 3 column vectors:

  • [m11, m21, m31] is the vector pointing to the right relative to the screen
  • [m12, m22, m32] is the vector pointing up relative to the screen
  • [m13, m23, m33] is the vector pointing directly out from the screen

These vectors are given in a coordinate space where the Z axis points directly up, e.g. opposite to earth's gravity.

What you asked for is to express this orientation as rotations from the upright position. For convenience later we will rearrange the matrix so that the order of the column vectors becomes out, right, up:

    return [
        m13, m11, m12,
        m23, m21, m22,
        m33, m31, m32
    ];
};

And since you are assigning the greatest importance to the rotation about the out axis (X) and the least importance to the rotation about the up axis (Z), we will decompose this matrix to rotations in the order X-Y-Z (code from here):

function getEulerAngles( matrix ) {
    var radtodeg = 180 / Math.PI; // Radian-to-Degree conversion
    var sy = Math.sqrt(matrix[0] * matrix[0] +  matrix[3] * matrix[3] );
 
    var singular = sy < 1e-6; // If
 
    if (!singular) {
        var x = Math.atan2(matrix[7] , matrix[8]);
        var y = Math.atan2(-matrix[6], sy);
        var z = Math.atan2(matrix[3], matrix[0]);
    } else {
        var x = Math.atan2(-matrix[5], matrix[4]);
        var y = Math.atan2(-matrix[6], sy);
        var z = 0;
    }
    return [radtodeg * x, radtodeg * y, radtodeg * z];
}

Now, to get the representation you want, simply write:

var rotation = getEulerAngles(getRotationMatrix(alpha, beta, gamma));
  • rotation[0] is the steering wheel angle that you wanted for the upright position.
  • rotation[1] is the tilt angle.
  • rotation[2] is the "compass" angle that you were not interested in.

I have uploaded a working demo that you can access from your phone browser here.

Oddwarg
  • 31
  • 2
-1

I'm afraid that obtaining the info you are looking for only through the deviceorientation event is not possible precisely for the reason you say yourself: gimbal lock.

There are hordes of mathematicians studying in this regard, the result we can learn from their studies is that the only solution to the gimbal lock problem is to avoid it: when we start to get too close, we have to change our approach.

An alternative way to obtain that angle (which works only when the phone is almost vertical, so exactly when we need an alternative) could be to use devicemotion data rather than deviceorientation data.

window.addEventListener("devicemotion", event => {
  const { x } = event.accelerationIncludingGravity;

  const radiantsRes = Math.asin(x / 9.80665);
  const degreesRes = (radiantsRes * 180) / Math.PI;

  console.log(degreesRes.toFixed(2), radiantsRes.toFixed(10))
});
Daniele Ricci
  • 15,422
  • 1
  • 27
  • 55
  • The data from the `deviceorientation` is precise, nothing wrong there. The problem is that the OP doesn't know how to convert that into the format he needs (but also doesn't really tell us what that is). – Bergi Nov 07 '21 at 17:07
  • Hi @Bergi, actually what he need is crystal clear... and that we **can't** obtain that only through `deviceorientation` is crystal clear as well! I know `deviceorientation` data is so accurate, but when we come too close to the _gimbal lock_ points (device vertical, and device vertical upside down). What I can't understand is why it so hard to understand that the _gimbal lock_ problem can't be solved, at least with the current human knowledge :) – Daniele Ricci Nov 07 '21 at 18:15
  • Can you explain to me what he needs then, please? – Bergi Nov 07 '21 at 18:59
  • Sure @Bergi , the angle of the rotation of the device around the axis parallel to ground and perpendicular to the screen (when device is vertical) regardless of the angle of rotation around the axis parallel both to the ground and to the screen. In other words: the angle between the vertical line an the plane perpendicular to the screen which divides the screen in a left side and a right side (when device is vertical). When the device is in landscape position, `orientation.beta` itself is that angle, when the device is portrait position, orientation data is not enough to compute that angle. – Daniele Ricci Nov 07 '21 at 19:42