Android SensorManager странно, как переназначить систему координат

Демоверсии API -> Графика -> Компас

Он работает только правильно, пока вы не измените естественную ориентацию устройства. В большинстве телефонов это портрет, а в большинстве 10-дюймовых планшетов это пейзаж. Если вы измените, чем нужно повернуть это на 90 градусов. Я хотел бы увидеть 3D-решение для этой системы.

На 100% обязательно нужно использовать метод remapCoordinateSystem().

Я хотел бы увидеть, как (код), если бы я мог увидеть объяснение того, как рассчитывается отображение этих осей (теоретическая математика), было бы неплохо.

Я пытался понять, но я забыл всю линейную алгебру.

Здесь говорится, почему мы должны использовать, но не говорит, как!

float R[] = new float[9];
// X  (product of Y and Z) and roughly points East
// Y: points to Magnetic NORTH and tangential to ground
// Z: points to SKY and perpendicular to ground
float I[] = new float[9];
boolean success = SensorManager.getRotationMatrix(R, I, mGravity, mGeomagnetic);

Кажется, эти координаты находятся в этой позиции: - устройство говорит в таблице (оси x и y находятся в таблице)

ориентация устройства

Только и только если

getWindowManager().getDefaultDisplay().getRotation() == Surface.ROTATION_0

Вопрос в том, как завершить этот код:

switch (mScreenRotation) {
    case Surface.ROTATION_0:
    Log.v("SurfaceRemap", "0 degree");
    axisX = SensorManager.AXIS_X;// is this valid?
    axisY = SensorManager.AXIS_Y;// is this valid?
    break;

    case Surface.ROTATION_90:
    Log.v("SurfaceRemap", "90 degree");
    // examples says remapCoordinateSystem(inR, AXIS_Y, AXIS_MINUS_X, outR);
    axisX = SensorManager.AXIS_Y;
    axisY = SensorManager.AXIS_MINUS_X;
    break;

    case Surface.ROTATION_180:
    Log.v("SurfaceRemap", "180 degree");
    break;

    case Surface.ROTATION_270:
    Log.v("SurfaceRemap", "270 degree");
    break;

    default:
    Log.v("SurfaceRemap", "don't know the mScreenRotation value: "+mScreenRotation+" you should never seen this message!");
    break;
}


boolean remapped = SensorManager.remapCoordinateSystem(R, axisX, axisY, R);

float orientation[] = new float[3];

SensorManager.getOrientation(R, orientation);// All three angles above are in radians and positive in the counter-clockwise direction.
inclination = SensorManager.getInclination(I);

Изменить: я написал небольшое тестовое приложение, где на экране оно отображает поворот экрана: 0, 90, 270 градусов (не могу сделать 180 сейчас)

Кажется, если Вращение 0 не изменилось (axisX = SensorManager.AXIS_X;axisY = SensorManager.AXIS_Y;) чем 90 градусов должно быть:

axisX = SensorManager.AXIS_MINUS_Y;
axisY = SensorManager.AXIS_X;

чем документация гугл говорит где то неверные значения! Вопрос где?!

getRotationMatrix возвращает это:

X определяется как векторное произведение YZ (оно является касательным к земле в текущем местоположении устройства и приблизительно указывает на восток).

Y является касательной к земле в текущем местоположении устройства и указывает на магнитный северный полюс.

Z указывает на небо и перпендикулярно земле.

Смотри телефон выше! Я хочу, чтобы справа налево, с задней камерой на землю.

getOrientation возвращает это:

X определяется как векторное произведение YZ (оно является касательным к земле в текущем местоположении устройства и приблизительно указывает на запад).

Y является касательной к земле в текущем местоположении устройства и указывает на магнитный северный полюс.

Z указывает на центр Земли и перпендикулярно земле.

values[0]: азимут, вращение вокруг оси Z.

values[1]: шаг, вращение вокруг оси X.

values[2]: крен, вращение вокруг оси Y.

Как должен быть телефон?

Наконец, я хотел бы иметь значения углов, как у самолетов. Мой телефон (я) направляется на север: (рыскание по азимуту)

              if ScreenRotation = 0 degree
Pitch axis = -orientationAxisX  =  rotationAxisX
Roll axis  =  orientationAxisY  =  rotationAxisY 
Yaw axis   =  orientationAxisZ  = -rotationAxisZ

5 ответов

Чтобы завершить переключение ветвей, я просто пытаюсь подумать, следуя методу remapCoordinateSystem javadoc:

X определяет, на какую мировую ось и направление отображается ось X устройства.
Y определяет, на какую мировую ось и направление нанесена ось Y устройства.

Так что возьмите ваше устройство, поверните его от его естественной ориентации (90, 180 или 270 градусов) и спросите себя: положительная ось X в исходной ориентации устройства, какой оси соответствует текущая ориентация устройства?. И то же самое для оси Y.

Таким образом, если ваше устройство повернуто на 90 градусов, вы увидите, что исходная положительная ось X соответствует текущей положительной оси Y, а исходная положительная ось Y соответствует отрицательной оси X текущей ориентации.

Так и должно быть:

switch (mScreenRotation) {
    case Surface.ROTATION_0:
        axisX = SensorManager.AXIS_X;
    axisY = SensorManager.AXIS_Y;
        break;

    case Surface.ROTATION_90:
        axisX = SensorManager.AXIS_Y;
    axisY = SensorManager.AXIS_MINUS_X;
        break;

    case Surface.ROTATION_180:
        axisX = SensorManager.AXIS_MINUS_X;
    axisY = SensorManager.AXIS_MINUS_Y;
        break;

    case Surface.ROTATION_270:
        axisX = SensorManager.AXIS_MINUS_Y;
    axisY = SensorManager.AXIS_X;
        break;

    default:
        break;
}

Это сработало для меня, надеюсь, что помогает.

Спасибо keianhzo, ваш ответ отлично работает с телефонами на земле. Для AR-приложений, где вы смотрите "сквозь" дисплей, я обнаружил, что это работает: используйте правильную ось:

int screenRotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
//use the correct axis
int axisX = SensorManager.AXIS_X;
int axisY = SensorManager.AXIS_Y;
switch (mMode) {
    case LOOK_THROUGH: {
        // look through always uses x and z
        axisX = SensorManager.AXIS_X;
        axisY = SensorManager.AXIS_Z;
        break;
    }
    case FLAT: {
        // flat changes the x axis depending on rotation state
        switch (screenRotation) {
            case Surface.ROTATION_0:
                axisX = SensorManager.AXIS_X;
                axisY = SensorManager.AXIS_Y;
                break;
            case Surface.ROTATION_90:
                axisX = SensorManager.AXIS_Y;
                axisY = SensorManager.AXIS_MINUS_X;
                break;
            case Surface.ROTATION_180:
                axisX = SensorManager.AXIS_MINUS_X;
                axisY = SensorManager.AXIS_MINUS_Y;
                break;
            case Surface.ROTATION_270:
                axisX = SensorManager.AXIS_MINUS_Y;
                axisY = SensorManager.AXIS_X;
                break;
            default:
                break;
        }
        break;
    }
    default:
        break;
}

Получите градусы ориентации:

boolean success = SensorManager.remapCoordinateSystem(getQuaternion().getMatrix4x4().getMatrix(), axisX, axisY, mRotationMatrixTransformed);
if (success) {
    SensorManager.getOrientation(mRotationMatrixTransformed, mOrientationValues);

    for (int i = 0; i < 3; i++) {
        mOrientationDegrees[i] = (float) Math.toDegrees(mOrientationValues[i]);
    }
//And for look through, add the rotation state
    if (mMode == MODE.LOOK_THROUGH) {
    // look through has different angles depending on rotation state
    switch (screenRotation) {
        case Surface.ROTATION_90: {
            mOrientationDegrees[2] += 90;
            break;
        }
        case Surface.ROTATION_180: {
            mOrientationDegrees[2] += 180;
            break;
        }
        case Surface.ROTATION_270: {
            mOrientationDegrees[2] += 270;
            break;
        }
    }
}

Вот как я делаю магию в своем приложении:

        float[] rotationMatrixOrig = new float[9];
        SensorManager.getRotationMatrix(rotationMatrixOrig, null, lastAccelerometerValue, lastMagnetometerValue);

        int screenRotation = app.getCurrentActivity().getWindowManager().getDefaultDisplay().getRotation();
        int axisX, axisY;
        boolean isUpSideDown = lastAccelerometerValue[2] < 0;

        switch (screenRotation) {
            case Surface.ROTATION_0:
                axisX = (isUpSideDown ? SensorManager.AXIS_MINUS_X : SensorManager.AXIS_X);
                axisY = (Math.abs(lastAccelerometerValue[1]) > 6.0f ? 
                        (isUpSideDown ? SensorManager.AXIS_MINUS_Z : SensorManager.AXIS_Z) :
                        (isUpSideDown ? SensorManager.AXIS_MINUS_Y : SensorManager.AXIS_Y));
                break;
            case Surface.ROTATION_90:
                axisX = (isUpSideDown ? SensorManager.AXIS_MINUS_Y : SensorManager.AXIS_Y);
                axisY = (Math.abs(lastAccelerometerValue[0]) > 6.0f ? 
                        (isUpSideDown ? SensorManager.AXIS_Z : SensorManager.AXIS_MINUS_Z) :
                        (isUpSideDown ? SensorManager.AXIS_X : SensorManager.AXIS_MINUS_X));
                break;
            case  Surface.ROTATION_180:
                axisX = (isUpSideDown ? SensorManager.AXIS_X : SensorManager.AXIS_MINUS_X);
                axisY = (Math.abs(lastAccelerometerValue[1]) > 6.0f ? 
                        (isUpSideDown ? SensorManager.AXIS_Z : SensorManager.AXIS_MINUS_Z) :
                        (isUpSideDown ? SensorManager.AXIS_Y : SensorManager.AXIS_MINUS_Y));
                break;
            case Surface.ROTATION_270:
                axisX = (isUpSideDown ? SensorManager.AXIS_Y : SensorManager.AXIS_MINUS_Y);
                axisY = (Math.abs(lastAccelerometerValue[0]) > 6.0f ? 
                        (isUpSideDown ? SensorManager.AXIS_MINUS_Z : SensorManager.AXIS_Z) :
                        (isUpSideDown ? SensorManager.AXIS_MINUS_X : SensorManager.AXIS_X));
                break;
            default:
                axisX = (isUpSideDown ? SensorManager.AXIS_MINUS_X : SensorManager.AXIS_X);
                axisY = (isUpSideDown ? SensorManager.AXIS_MINUS_Y : SensorManager.AXIS_Y);
        }

        float[] rotationMatrix = new float[9];
        SensorManager.remapCoordinateSystem(rotationMatrixOrig, axisX, axisY, rotationMatrix);

Если пользовательский интерфейс телефона заблокирован на вращение 0, я получаю следующие значения без remapCoordinateSystem()

Pitch (phone) = -Pitch   (API)
Roll  (phone) =  Roll     (API)
Yaw   (phone) =  Azimuth  (API)
  • минимум около 0,0,0 значения.

Если пользовательский интерфейс телефона вынужден повернуть на 90:

Значение рыскания имеет -90 градусов ( - PI/2) при старой ориентации!!! => Я пойду на Восток, а не на Север.

Если я возьму телефон в положение 0,0,0:

Pitch (phone) = -Roll    (API)
Roll  (phone) = -Pitch   (API)
Yaw   (phone) =  Azimuth (API)

Если пользовательский интерфейс телефона принудительно поворачивается на 180:

Значение рыскания имеет +/-180 градусов ( +/- PI) при старой ориентации!!! => Я поеду на юг, а не на север.

Если я возьму телефон в положение 0,0,0:

Pitch (phone) =  Pitch   (API)
Roll  (phone) = -Roll    (API)
Yaw   (phone) =  Azimuth (API)

Если телефонный интерфейс вынужден вращаться 270:

Значение рыскания имеет +90 градусов ( + PI/2) при старой ориентации!!! => Я пойду на Запад в реальности, а не на север.

Если я возьму телефон в положение 0,0,0:

Pitch (phone) =  Roll    (API)
Roll  (phone) =  Pitch   (API)
Yaw   (phone) =  Azimuth (API)

Я написал небольшое исправление и проверил: android:screenOrientation="fullSensor"

public static final void fixRotation0(float[] orientation) { //azimuth, pitch, roll
    orientation[1] = -orientation[1]; // pitch = -pitch
}

public static final void fixRotation90(float[] orientation) { //azimuth, pitch, roll
    orientation[0] += Math.PI / 2f; // offset
    float tmpOldPitch = orientation[1];
    orientation[1] = -orientation[2]; //pitch = -roll
    orientation[2] = -tmpOldPitch; // roll  = -pitch    
}

public static final void fixRotation180(float[] orientation) { //azimuth, pitch, roll
    orientation[0] = (float)(orientation[0] > 0f ? (orientation[0] - Math.PI) : (orientation[0] + Math.PI)); // offset
    orientation[2] = -orientation[2]; // roll = -roll
}

public static final void fixRotation270(float[] orientation) { //azimuth, pitch, roll
    orientation[0] -= Math.PI / 2; // offset
    float tmpOldPitch = orientation[1];
    orientation[1] = orientation[2]; //pitch = roll
    orientation[2] = tmpOldPitch; // roll  = pitch  
}

В большинстве случаев работает. При быстром повороте на 180 градусов вокруг 1 оси система будет закручена!

Полный код доступен на Github

Что я делаю

  1. переназначить систему координат, как предложил Кейанзо в своем ответе, переназначить координаты в соответствии с поворотом экрана
  2. затем я переназначаю полученную систему координат снова
    SensorManager.remapCoordinateSystem(rotationMatrixScreenRemapped, SensorManager.AXIS_X, SensorManager.AXIS_Z, rotationMatrixCameraRemapped);
    переназначить координаты в соответствии с камерой (в стиле AR), как указано в документации

Пока я надеюсь, что это работает хорошо!

Другие вопросы по тегам