Поиск вектора нормали к устройству iOS

Я хотел бы использовать CMAttitude, чтобы узнать вектор нормали к стеклу экрана iPad/iPhone (относительно земли). Таким образом, я бы получил такие векторы:

введите здесь описание изображения

Обратите внимание, что это отличается от ориентации тем, что мне все равно, как устройство вращается вокруг оси Z. Поэтому, если бы я держал iPad над головой лицевой стороной вниз, он отображал бы (0,-1,0), и даже когда я вращал его над головой (как вертолет), он продолжал бы читать (0,- 1,0):

введите здесь описание изображения

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


person Francisco Ryan Tolmasky I    schedule 31.05.2012    source источник
comment
Вам нужен вектор силы тяжести?   -  person anticyclope    schedule 31.05.2012
comment
Вектор гравитации не работает. На первом изображении выше вектор гравитации одинаков для левого (1,0,0) и правого (-1,0,0) примеров.   -  person Francisco Ryan Tolmasky I    schedule 31.05.2012
comment
Кстати, какое это имеет отношение к кватернионам? Кватернионы состоят из четырех элементов (отсюда и quat (квадрат) в названии). Разве это не просто тангаж и рыскание?   -  person borrrden    schedule 31.05.2012
comment
Это относится ко всем формам представления ориентации (будь то кватернионы, углы Эйлера или матрицы поворота), поскольку каждый из них должен быть преобразован в нужное мне значение. тангаж/рыскание/крен - это просто другой способ представления той же информации, что и кватернионы (хуже способ, поскольку он страдает от блокировки карданчика). Кватернионы сегодня являются предпочтительным способом измерения ориентации по этой и многим другим причинам.   -  person Francisco Ryan Tolmasky I    schedule 31.05.2012
comment
Что ж, тогда вы должны добавить данные магнитометра к показаниям силы тяжести.   -  person anticyclope    schedule 31.05.2012
comment
Теория: нормаль уравнения плоскости должна быть такой, какой вы хотите paulbourke.net/geometry/planeeq и плоскость будет определяться двумя векторами (которые вы получаете от акселерометра и/или CoreMotion)   -  person nacho4d    schedule 31.05.2012


Ответы (2)


  1. В вашем случае мы можем сказать, что вращение устройства равно вращению нормали устройства (вращение вокруг самой нормали просто игнорируется, как вы это указали)
  2. CMAttitude, который можно получить через CMMotionManager.deviceMotion обеспечивает вращение относительно опорного кадра. Его свойства кватернион, матрица вращения и углы Эйлера — это просто разные представления.
  3. Опорный кадр можно указать при запуске обновлений движения устройства с помощью startDeviceMotionUpdatesUsingReferenceFrame. До iOS 4 вам приходилось использовать умножитьByInverseOfAttitude

Собрав это вместе, вам просто нужно умножить кватернион правильным образом на вектор нормали, когда устройство лежит на столе лицевой стороной вверх. Теперь нам нужен этот правильный способ умножения кватернионов, представляющий поворот: согласно Вращение векторов это делается с помощью:

n = q * e * q', где q – кватернион, предоставленный CMAttitude [w, (x, y, z)], q' является его сопряженным [w, (-x, -y, -z)] и e является кватернионным представлением грани вверх по нормали [0, (0, 0, 1)]. К сожалению, Apple CMQuaternion является структурой, поэтому вам нужен небольшой вспомогательный класс.

Quaternion e = [[Quaternion alloc] initWithValues:0 y:0 z:1 w:0];
CMQuaternion cm = deviceMotion.attitude.quaternion;
Quaternion quat = [[Quaternion alloc] initWithValues:cm.x y:cm.y z:cm.z w: cm.w];
Quaternion quatConjugate = [[Quaternion alloc] initWithValues:-cm.x y:-cm.y z:-cm.z w: cm.w];
[quat multiplyWithRight:e];
[quat multiplyWithRight:quatConjugate];
// quat.x, .y, .z contain your normal

Кватернион.h:

@interface Quaternion : NSObject {
    double w;
    double x;
    double y;
    double z;
}

@property(readwrite, assign)double w;
@property(readwrite, assign)double x;
@property(readwrite, assign)double y;
@property(readwrite, assign)double z;

Кватернион.м:

- (Quaternion*) multiplyWithRight:(Quaternion*)q {
    double newW = w*q.w - x*q.x - y*q.y - z*q.z;
    double newX = w*q.x + x*q.w + y*q.z - z*q.y;
    double newY = w*q.y + y*q.w + z*q.x - x*q.z;
    double newZ = w*q.z + z*q.w + x*q.y - y*q.x;
    w = newW;
    x = newX;
    y = newY;
    z = newZ;
    // one multiplication won't denormalise but when multipling again and again 
    // we should assure that the result is normalised
    return self;
}

- (id) initWithValues:(double)w2 x:(double)x2 y:(double)y2 z:(double)z2 {
        if ((self = [super init])) {
            x = x2; y = y2; z = z2; w = w2;
        }
        return self;
}

Я знаю, что кватернионы поначалу немного странные, но как только у вас появится идея, они станут действительно блестящими. Это помогло мне представить кватернион как вращение вокруг вектора (x, y, z), а w — это (косинус) угла.

Если вам нужно сделать с ними больше, взгляните на cocoamath проект с открытым исходным кодом. Классы Quaternion и его расширение QuaternionOperations являются хорошей отправной точкой.

Для полноты, да, вы можете сделать это и с умножением матриц:

n = M * e

Но я бы предпочел кватернион, поскольку он избавляет вас от всех тригонометрических хлопот и работает лучше.

person Kay    schedule 31.05.2012
comment
Как я вижу, у меня похожий вопрос. Не могли бы вы помочь мне с этой математикой кватернионов iOS? stackoverflow.com/questions/25363781/ - person gleb.kudr; 19.08.2014
comment
Отличный общий ответ, но обратите внимание, что в этом конкретном случае части x, y, z кватерниона уже дают ориентацию, о которой просил ОП. - person fishinear; 23.04.2016

Спасибо Кею за отправную точку решения. Вот моя реализация для тех, кому это нужно. Я сделал пару небольших поправок по совету Кея для моей ситуации. В качестве предупреждения я использую только альбомную презентацию. У меня есть код, который обновляет переменную _isLandscapeLeft, чтобы внести необходимые изменения в направление вектора.

Кватернион.h

    @interface Quaternion : NSObject{
    //double w;
    //double x;
    //double y;
    //double z;
}

@property(readwrite, assign)double w;
@property(readwrite, assign)double x;
@property(readwrite, assign)double y;
@property(readwrite, assign)double z;

- (id) initWithValues:(double)w2 x:(double)x2 y:(double)y2 z:(double)z2;
- (Quaternion*) multiplyWithRight:(Quaternion*)q;
@end

Кватернион.м

#import "Quaternion.h"

@implementation Quaternion


- (Quaternion*) multiplyWithRight:(Quaternion*)q {
    double newW = _w*q.w - _x*q.x - _y*q.y - _z*q.z;
    double newX = _w*q.x + _x*q.w + _y*q.z - _z*q.y;
    double newY = _w*q.y + _y*q.w + _z*q.x - _x*q.z;
    double newZ = _w*q.z + _z*q.w + _x*q.y - _y*q.x;
    _w = newW;
    _x = newX;
    _y = newY;
    _z = newZ;
    // one multiplication won't denormalise but when multipling again and again
    // we should assure that the result is normalised
    return self;
}

- (id) initWithValues:(double)w2 x:(double)x2 y:(double)y2 z:(double)z2 {
    if ((self = [super init])) {
        _x = x2; _y = y2; _z = z2; _w = w2;
    }
    return self;
}


@end

И мой игровой класс, использующий кватернион для стрельбы:

-(void)fireWeapon{
    ProjectileBaseClass *bullet = [[ProjectileBaseClass alloc] init];
    bullet.position = SCNVector3Make(0, 1, 0);
    [self.rootNode addChildNode:bullet];

    Quaternion *e = [[Quaternion alloc] initWithValues:0 x:0 y:0 z:1];
    CMQuaternion cm = _currentAttitude.quaternion;
    Quaternion *quat = [[Quaternion alloc] initWithValues:cm.w x:cm.x y:cm.y z:cm.z];
    Quaternion *quatConjugate = [[Quaternion alloc] initWithValues:cm.w x:-cm.x y:-cm.y z:-cm.z];
    quat = [quat multiplyWithRight:e];
    quat = [quat multiplyWithRight:quatConjugate];
    SCNVector3 directionToShoot;
    if (_isLandscapeLeft) {
        directionToShoot = SCNVector3Make(quat.y, -quat.x, -quat.z);

    }else{
        directionToShoot = SCNVector3Make(-quat.y, quat.x, -quat.z);

    }

    SCNAction *shootBullet = [SCNAction moveBy:directionToShoot duration:.1];
    [bullet runAction:[SCNAction repeatActionForever:shootBullet]];
}
person Metalboyblue    schedule 13.02.2015