NOTE: You must view the page with https and NOT http
DISCLAIMER: We are not affiliated with any of the parties found on the Gyro-Cube demo page.
The [now-solved] problem starts with...
window.addEventListener("deviceorientation", handleDeviceTilt);
function handleDeviceTilt(event){
// Here we can use event.beta, event.gamma
// Note that event.alpha is just the compass
// alpha values are w.r.t. Earth's magnetic poles in the real world
// In theory, we don't need to know which way is north to build a simple bubble-level app
// or a simple driving simulator.
}
...where 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 UPRIGHT (like parallel to a wall) we can't get realistic values. gamma
gets more and more crazy as the device approaches the perfect vertical position (aligned with Earth's gravity) which is the most common way of holding a mobile device (i.e. portrait mode).
The question is,
What would be the simplest way to calculate the correct angles like
realBeta
andrealGamma
(perhaps by usingMath.cos()
andMath.sin()
or by some other method) in order to unlock the so called gimbal lock? Given that the "distortedness" or the "inaccuracy" in gamma is proportional in some way to beta, there must exist a solution but how exactly do we getrealGamma
oractualGamma
orfixedCorrectGamma
?
Note 1: There are a few spirit level apps on PlayStore which suggest 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 but acceptable approximations are also welcome. However, the closer we get to exact angles the better. If necessary alpha
may be omitted for simplicity.
By solving this problem we would be able to use deviceorientation
to make something similar to this,
...or for example we could use gamma (or some kind of similar processed rotation data) to steer a car in a vertical racing game.
If the link HERE does not work please search and find any page that features some kind of live demo where it will let you see what numbers are reported by your mobile device. Watch γ (gamma
) values carefully as beta
approaches 90deg.
Final thought,
We may need some arcane math-magics.
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:
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));
I have uploaded a working demo that you can access from your phone browser here.