4

I am working on a tilt app for Android. I am having an issue with Portrait & landscape mode. When the pitch = 90 degrees (phone on end) and even slightly before the roll value goes crazy when there has been no physical change in roll. I have not been able to find a solution to this problem. If anyone can point me in the right direction, it would be appreciated.

Here’s a short code dump, so you know it is not an accelerometer error.

final SensorEventListener mEventListener = new SensorEventListener(){
     public void onAccuracyChanged(Sensor sensor, int accuracy) {}  
 public void onSensorChanged(SensorEvent event) {
     setListners(sensorManager, mEventListener);

      SensorManager.getRotationMatrix(mRotationMatrix, null, mValuesAccel, mValuesMagnet);
     SensorManager.getOrientation(mRotationMatrix, mValuesOrientation);


        synchronized (this) {

            switch (event.sensor.getType()){
                case Sensor.TYPE_ACCELEROMETER:

                    System.arraycopy(event.values, 0, mValuesAccel, 0, 3);

                    long actualTime = System.currentTimeMillis();

                    //Sensitivity delay
                    if (actualTime - lastUpdate < 250) {
                        return;
                        }
                    else {
                        sysAzimuth = (int)Math.toDegrees(mValuesOrientation[0]);
                        sysPitch = (int)Math.toDegrees(mValuesOrientation[1]);
                        sysRoll = (int)Math.toDegrees(mValuesOrientation[2]);

                        //invert direction with -1
                      pitch = (sysPitch - pitchCal)*-1;
                      roll = (sysRoll - rollCal);
                      azimuth = sysAzimuth;

                    lastUpdate = actualTime;
                    }
Undo
  • 25,519
  • 37
  • 106
  • 129
user1234051
  • 271
  • 1
  • 3
  • 9

4 Answers4

10

I found what I was looking for, Rotational Matrices.

I was using Euler angles (roll, pitch, yaw) for the pitch and roll. When the phone is on end 90 degrees, the x and z plain are the same and the phone goes crazy, a fundamental flaw with Euler angles.

I need to get the pitch and roll degrees using Rotational Matrices via getRotationMatrix

Here it is for all ;)

XML:

<?xml version="1.0" encoding="utf-8"?>
<!-- This file is res/layout/main.xml -->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<Button android:id="@+id/update" android:text="Update Values"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doUpdate" />
<Button android:id="@+id/show" android:text="Show Me!"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doShow" android:layout_toRightOf="@id/update" />
<TextView android:id="@+id/preferred" android:textSize="20sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/update" />
<TextView android:id="@+id/orientation" android:textSize="20sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/preferred" />
</RelativeLayout>

Code:

package YOURPACKAGE;



import android.app.Activity;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;


public class YOURCLASS extends Activity implements SensorEventListener {
private static final String TAG = "VirtualJax";
private SensorManager mgr;
private Sensor accel;
private Sensor compass;
private Sensor orient;
private TextView preferred;
private TextView orientation;
private boolean ready = false;
private float[] accelValues = new float[3];
private float[] compassValues = new float[3];
private float[] inR = new float[9];
private float[] inclineMatrix = new float[9];
private float[] orientationValues = new float[3];
private float[] prefValues = new float[3];
private float mAzimuth;
private double mInclination;
private int counter;
private int mRotation;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    preferred = (TextView)findViewById(R.id.preferred);
    orientation = (TextView)findViewById(R.id.orientation);
    mgr = (SensorManager) this.getSystemService(SENSOR_SERVICE);
    accel = mgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    compass = mgr.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
    orient = mgr.getDefaultSensor(Sensor.TYPE_ORIENTATION);
    WindowManager window = (WindowManager) this.getSystemService(WINDOW_SERVICE);
    int apiLevel = Integer.parseInt(Build.VERSION.SDK);
    if(apiLevel <8) {
        mRotation = window.getDefaultDisplay().getOrientation();
    }
    else {
        mRotation = window.getDefaultDisplay().getRotation();
    }
}

@Override
protected void onResume() {
    mgr.registerListener(this, accel, SensorManager.SENSOR_DELAY_GAME);
    mgr.registerListener(this, compass, SensorManager.SENSOR_DELAY_GAME);
    mgr.registerListener(this, orient, SensorManager.SENSOR_DELAY_GAME);
    super.onResume();
}

@Override
protected void onPause() {
    mgr.unregisterListener(this, accel);
    mgr.unregisterListener(this, compass);
    mgr.unregisterListener(this, orient);
    super.onPause();
}

public void onAccuracyChanged(Sensor sensor, int accuracy) {
    // ignore
}

public void onSensorChanged(SensorEvent event) {
    // Need to get both accelerometer and compass
    // before we can determine our orientationValues
    switch(event.sensor.getType()) {
        case Sensor.TYPE_ACCELEROMETER:
            for(int i=0; i<3; i++) {
                accelValues[i] = event.values[i];
            }
            if(compassValues[0] != 0)
                ready = true;
            break;
        case Sensor.TYPE_MAGNETIC_FIELD:
            for(int i=0; i<3; i++) {
                compassValues[i] = event.values[i];
            }
            if(accelValues[2] != 0)
                ready = true;
            break;
        case Sensor.TYPE_ORIENTATION:
            for(int i=0; i<3; i++) {
                orientationValues[i] = event.values[i];
            }
            break;
    }

    if(!ready)
        return;
    if(SensorManager.getRotationMatrix(inR, inclineMatrix, accelValues, compassValues)) {
        // got a good rotation matrix
        SensorManager.getOrientation(inR, prefValues);
        mInclination = SensorManager.getInclination(inclineMatrix);
        // Display every 10th value
        if(counter++ % 10 == 0) {
            doUpdate(null);
            counter = 1;
        }

    }
}

public void doUpdate(View view) {
    if(!ready)
        return;
    mAzimuth = (float) Math.toDegrees(prefValues[0]);
    if(mAzimuth < 0) {
        mAzimuth += 360.0f;
    }
    String msg = String.format(
            "Preferred:\nazimuth (Z): %7.3f \npitch (X): %7.3f\nroll (Y): %7.3f",
            mAzimuth, Math.toDegrees(prefValues[1]),
            Math.toDegrees(prefValues[2]));
    preferred.setText(msg);
    msg = String.format(
            "Orientation Sensor:\nazimuth (Z): %7.3f\npitch (X): %7.3f\nroll (Y): %7.3f",
            orientationValues[0],
            orientationValues[1],
            orientationValues[2]);
    orientation.setText(msg);
    preferred.invalidate();
    orientation.invalidate();
}

public void doShow(View view) {
    // google.streetview:cbll=30.32454,-81.6584&cbp=1,yaw,,pitch,1.0
    // yaw = degrees clockwise from North
    // For yaw we can use either mAzimuth or orientationValues[0].
    //
    // pitch = degrees up or down. -90 is looking straight up,
    // +90 is looking straight down
    // except that pitch doesn't work properly
    Intent intent=new Intent(Intent.ACTION_VIEW, Uri.parse(
            "google.streetview:cbll=30.32454,-81.6584&cbp=1," +
                    Math.round(orientationValues[0]) + ",,0,1.0"
    ));
    startActivity(intent);
    return;
}
Tim Autin
  • 6,043
  • 5
  • 46
  • 76
user1234051
  • 271
  • 1
  • 3
  • 9
  • I'm having the same problem, although I'm using rotational matrices as well. I'm using getRotationMatrixFromVector() though instead of getRotationMatrix(), because I'm using TYPE_ROTATION_VECTOR. Are there any differences between these two methods? Second question is what is thisfor in you code if(mAzimuth < 0) { mAzimuth += 360.0f; }? – Nazerke Mar 14 '13 at 11:31
  • Can't comment on the vector, but the mAzimuth is the compass heading, it doesn't provide it as a clean 0-360 reading, you need to convert the neg values back to a 360, plus alot of other tweaks if you want to to calculate correctly. You would think you could just ask the device for the pitch, roll and compass heading and get it back, no way, you get to figure it out, a major PITA for what should have been simple. – user1234051 Mar 14 '13 at 15:00
  • What are those other tweaks? What do you end up doing with mInclination? Is that part of the correction? I am using getRotationMatrixFromVector() as well and seeing the azimuth vary with pitch, which I do not want, and Google maps does not do in compass mode. – Flyview Mar 16 '16 at 18:03
  • This solved my problem, but it's worth mentioning that this solution uses things that might not be common to everyone, like the arrays being Passed by Reference (e.i. the value is set on the parameter passed to the methods). This might confuse some people, but it is how those methods are implemented, so there's no much we can do about it. Also, some variables are not well named, like inR that could be called rotationMatrix. – Leonardo Sibela Apr 16 '20 at 18:20
2

I wouldn't use Euler angles (roll, pitch, yaw). It pretty much screws up the stability of your app as you already noticed.

See here why, and what to do instead: Strange behavior with android orientation sensor.

Community
  • 1
  • 1
Ali
  • 56,466
  • 29
  • 168
  • 265
  • Thanks, I checked out the post, I clearly understand why the Euler angels are a bad choice, but I am not familiar with rotation matrices and can not find any good info about implementing them in Android. Any other references? – user1234051 Feb 26 '12 at 22:22
  • You do not have to implement it, it is implemented by the SensorManager. I would start at the [SensorManager.getRotationMatrix](http://developer.android.com/reference/android/hardware/SensorManager.html#getRotationMatrix). – Ali Feb 26 '12 at 23:07
  • Thanks, I've looked at that. Unfortunately I don't learn well from Google’s theoretical examples. I figure things out by looking at working code, new to the concept of rotation matrices and also Android. One heck of an uphill climb right now. – user1234051 Feb 26 '12 at 23:48
  • Unfortunately I do not do any Android programming so I cannot help you. Well, don't give up! – Ali Feb 27 '12 at 11:30
0

Through experimentation I found that when you switch from Portrait to Landscape mode your rotation matrix doesn't change but you have to change it manually in order to use with OpenGL correctly

copyMat(mRotationMatrixP, mRotationMatrix);

// permute and negate columns 0, 1
mRotationMatrixP[0] = -mRotationMatrix[1];
mRotationMatrixP[4] = -mRotationMatrix[5];
mRotationMatrixP[8] = -mRotationMatrix[9];

// permute 1, 0
mRotationMatrixP[1] = mRotationMatrix[0];
mRotationMatrixP[5] = mRotationMatrix[4];
mRotationMatrixP[9] = mRotationMatrix[8];

Also I hope you acquire the Rotation Matrix correctly on the first place:

public void onSensorChanged(SensorEvent event) {
    if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
        SensorManager.getRotationMatrixFromVector(
                mRotationMatrix , event.values);
        SensorManager.getOrientation (mRotationMatrix, values);
biegleux
  • 13,179
  • 11
  • 45
  • 52
Vlad
  • 4,425
  • 1
  • 30
  • 39
0

What you describe is called gimbal lock. At pitch +/-90, yaw -(+) roll is completely undefined. Near pitch +/-90, small nois/error in attitude can cause large fluctuations in yaw and roll individually even though there is no large change in actual orientation. Here is a great write-up on yaw, pitch roll (and how thay are not implemented well on many platforms):

http://www.sensorplatforms.com/understanding-orientation-conventions-mobile-platforms/

nak
  • 97
  • 2