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

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

enter image description here

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

enter image description here

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

Ответ 1

  • В вашем случае мы можем сказать, что вращение устройства равно вращению устройства в обычном режиме (вращение вокруг самой нормы просто игнорируется, как вы указали)
  • CMAttitude, который вы можете получить через CMMotionManager.deviceMotion обеспечивает вращение относительно опорной рамы. Его свойства quaternion, roation матрица и углы Эйлера - это просто разные представления.
  • Эталонный кадр может быть указан при запуске обновлений движений устройства с использованием метода CMMotionManager startDeviceMotionUpdatesUsingReferenceFrame. До iOS 4 вам пришлось использовать multiplyByInverseOfAttitude

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

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

Quaternion.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.m:

- (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

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

Ответ 2

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

Quaternion.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

Quaternion.m

#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]];
}