32

I tried to use the Z axis data from SensorEvent.values, but it doesn't detect rotation of my phone in the XY plane, ie. around the Z-axis.

I am using this as a reference for the co-ordinate axes. Is it correct?

axes

How do I measure that motion using accelerometer values?

These games do something similar: Extreme Skater, Doodle Jump.

PS: my phone orientation will be landscape.

Jason Sturges
  • 15,635
  • 14
  • 56
  • 75
udiboy1209
  • 1,332
  • 1
  • 13
  • 32

3 Answers3

70

Essentially, there is 2 cases here: the device is laying flat and not flat. Flat here means the angle between the surface of the device screen and the world xy plane (I call it the inclination) is less than 25 degree or larger than 155 degree. Think of the phone lying flat or tilt up just a little bit from a table.

First you need to normalize the accelerometer vector.
That is if g is the vector returns by the accelerometer sensor event values. In code

float[] g = new float[3]; 
g = event.values.clone();

double norm_Of_g = Math.sqrt(g[0] * g[0] + g[1] * g[1] + g[2] * g[2]);

// Normalize the accelerometer vector
g[0] = g[0] / norm_Of_g
g[1] = g[1] / norm_Of_g
g[2] = g[2] / norm_Of_g

Then the inclination can be calculated as

int inclination = (int) Math.round(Math.toDegrees(Math.acos(g[2])));

Thus

if (inclination < 25 || inclination > 155)
{
    // device is flat
}
else
{
    // device is not flat
}

For the case of laying flat, you have to use a compass to see how much the device is rotating from the starting position.

For the case of not flat, the rotation (tilt) is calculated as follow

int rotation = (int) Math.round(Math.toDegrees(Math.atan2(g[0], g[1])));

Now rotation = 0 means the device is in normal position. That is portrait without any tilt for most phone and probably landscape for tablet. So if you hold a phone as in your picture above and start rotating, the rotation will change and when the phone is in landscape the rotation will be 90 or -90 depends on the direction of rotation.

Hoan Nguyen
  • 17,479
  • 3
  • 47
  • 51
  • 5
    Brilliant! Usually people suggest using Sensor.TYPE_MAGNETIC_FIELD as well for this task, however you simplified the solution; and by the way accelerometer is the only sensor guaranteed to be present on Android. – southerton Sep 19 '14 at 12:20
  • 2
    Can i ask you what is the math behind the "Normalization of the acc vector?" and why you do atan(g[1]/g[0]) ( or atan(y/x) ) to get the degrees? ? @Hoan Nguyen – Andrea Baccega Feb 12 '16 at 07:23
  • @AndreaBaccega I forgot why I need to normalize or there is no need at all, just something I need to do for other thing. The atan(y/x) is just simple trigonometric calculation. To find the rotation, first you need to project the gravity to the xy plane. Now if there is no rotation this projection vector will have coordinate (0,1) (assuming normalization). If the device is rotated, this vector is the same but the coordinates change and the angle between this vector and the device y-coordinate is just tan(y/x). – Hoan Nguyen Feb 12 '16 at 19:30
  • 1
    @AndreaBaccega As far as I understood normalization is required to bring down the values in the range on -1 to 1, which is the acceptable range for `Math.acos()`, for numbers lying outside this range it returns NAN. There are some links if someone is interested: http://in.mathworks.com/help/matlab/ref/acos.html?requestedDomain=www.mathworks.com http://www.mathopenref.com/arccos.html – DeltaCap019 Feb 24 '16 at 09:24
  • brilliant solution and clean explaining – Fabio C. Sep 02 '16 at 14:04
  • 1
    can anybody tell, how to find angle when the "device is flat position"? Do I want to use "TYPE_MAGNETIC_FIELD"? "TYPE_MAGNETIC_FIELD" is not working in Lollipop(moto e). How to I do this? – SKK Sep 19 '16 at 13:23
  • @Karthi For rotation about the device z-axis when the device is flat you need to calculate the azimuth and hence the need for TYPE_MAGNETIC_FIELD, without it you are out of luck. – Hoan Nguyen Sep 19 '16 at 23:20
  • Great solution, the only mention that I have to make is regarding some false values when moving fast the phone along one axis. But this can be easily filtered. – Adrian C. Oct 13 '16 at 08:50
  • i know this is already past 3 years, but, can anyone pointing me out to some reference about this 'inclination' and/or math calculation about flat surface. i have a final project in college about this subject, for citation purpose. because stackverflow is not counted as reference.. – Egy Mohammad Erdin Nov 07 '16 at 20:56
  • HI, This solution works like charm for calculating tilt, but how to calculate the phone rotation keeping phone upright and Y axis steady? Can you please help? @HoanNguyen – Sanoop Surendran Mar 28 '17 at 12:23
  • Why do you use Math.acos(g[2]) not Math.acos(g[8]) ? – Thracian May 09 '17 at 11:25
  • @FatihOzcan There is no rotation matrix here. If you look at any linear algebra book you will see the angle between the plane containing the screen and the plane parallel to the ground is cos(g[2]) – Hoan Nguyen May 10 '17 at 00:01
  • This works like charm but there is something i wasn't able to overcome and all of the 3D compass i've downloaded so far has the same issue. If you move your device from laying flat position to standing position there occurs a difference sometimes about 10 degrees of azimuth. Wht does this occur and is there a way to fix it? – Thracian May 27 '17 at 18:55
  • This works in Android 10 if I use Sensor.TYPE_ROTATION_VECTOR rather than accelerometer. – UzumakiL Apr 09 '20 at 09:16
4

The accelerometer is sufficient for checking if the phone is flat as Hoan very nicely demonstrated.

For anyone who arrives here looking to not only check if the phone flat, but what the rotation of the phone is, it can be achieved through the Rotation Vector Motion Sensor.

private double pitch, tilt, azimuth;

@Override
public void onSensorChanged(SensorEvent event) {
    //Get Rotation Vector Sensor Values
    double[] g = convertFloatsToDoubles(event.values.clone());

    //Normalise
    double norm = Math.sqrt(g[0] * g[0] + g[1] * g[1] + g[2] * g[2] + g[3] * g[3]);
    g[0] /= norm;
    g[1] /= norm;
    g[2] /= norm;
    g[3] /= norm;

    //Set values to commonly known quaternion letter representatives
    double x = g[0];
    double y = g[1];
    double z = g[2];
    double w = g[3];

    //Calculate Pitch in degrees (-180 to 180)
    double sinP = 2.0 * (w * x + y * z);
    double cosP = 1.0 - 2.0 * (x * x + y * y);
    pitch = Math.atan2(sinP, cosP) * (180 / Math.PI);

    //Calculate Tilt in degrees (-90 to 90)
    double sinT = 2.0 * (w * y - z * x);
    if (Math.abs(sinT) >= 1)
        tilt = Math.copySign(Math.PI / 2, sinT) * (180 / Math.PI);
    else
        tilt = Math.asin(sinT) * (180 / Math.PI);

    //Calculate Azimuth in degrees (0 to 360; 0 = North, 90 = East, 180 = South, 270 = West)
    double sinA = 2.0 * (w * z + x * y);
    double cosA = 1.0 - 2.0 * (y * y + z * z);
    azimuth = Math.atan2(sinA, cosA) * (180 / Math.PI);
}

private double[] convertFloatsToDoubles(float[] input)
{
    if (input == null)
        return null;

    double[] output = new double[input.length];

    for (int i = 0; i < input.length; i++)
        output[i] = input[i];

    return output;
}

Then to check if the phone is flat you can simply compare the tilt and pitch values with a tolerance values. For example

public boolean flatEnough(double degreeTolerance) {
    return tilt <= degreeTolerance && tilt >= -degreeTolerance && pitch <= degreeTolerance && pitch >= -degreeTolerance;
}

The advantage to doing it this way is you can check if the phone is being held in any specific rotation.

It is worth noting that the app's orientation will not affect the values of pitch, tilt, and azimuth.

Dan
  • 5,704
  • 4
  • 31
  • 75
  • 1
    Why is the tilt between -90 to 90? What if i need to find / tilt vs \ tilt? – Vairavan Jan 24 '19 at 19:28
  • 1
    when i tried to compile this it failed with an ArrayIndexOutOfBounds on g[3] as length was 3... the solution i came up with for producing a value from 90 (vertical pointing upwards) to -90 (vertical pointing downwards) with 0 meaning the phone was in a horizontal position was replacing the `sinT` equation for `sinT = (g[1] - g[2] * g[0]).toDouble() ` – davy307 Apr 24 '19 at 14:28
3

Working off of the perfect response from @Dan

He missed a very slight bit of information that @davy307 pointed out.

When initializing the mAccelerometer, you must define it as Sensor.TYPE_ROTATION_VECTOR otherwise, it will not have the 3rd rotation vector and throw an ArrayIndexOutOfBounds exception.

mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);

Otherwise, this is a perfect solution... Appreciated!

Dayan
  • 43
  • 2
  • 11