26

我想使用 CMAttitude 来了解与 iPad/iPhone 屏幕玻璃垂直的矢量(相对于地面)。因此,我会得到如下向量:

在此处输入图像描述

请注意,这与方向不同,因为我不关心设备如何围绕 z 轴旋转。因此,如果我将 iPad 面朝下举过头顶,它会显示 (0,-1,0),即使我在头顶上方旋转它(就像直升机一样),它也会继续显示 (0,- 1,0):

在此处输入图像描述

我觉得这可能很容易,但由于我是四元数的新手并且不完全理解设备运动的参考框架选项,所以它整天都在逃避我。

4

2 回答 2

22
  1. 在您的情况下,我们可以说设备的旋转等于设备法线的旋转(围绕法线本身的旋转就像您指定的那样被忽略)
  2. 您可以通过 CMMotionManager.deviceMotion 获得 CMAttitude提供 相对于参考框架的旋转。它的性质四元数、旋转矩阵和欧拉角只是不同的表示。
  3. 当您使用 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 是 struct ,因此您需要一个小的帮助类。

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;

四元数.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

但我更喜欢四元数的方式,它可以为你省去所有三角函数的麻烦并且表现更好。

于 2012-05-31T15:45:36.527 回答
3

感谢 Kay 提出解决方案的起点。这是我对任何需要它的人的实现。我花了几个星期来听凯对我的情况提出的建议。作为提醒,我正在使用仅横向演示。我有更新变量 _isLandscapeLeft 的代码,以对向量的方向进行必要的调整。

四元数

    @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

四元数.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]];
}
于 2015-02-13T11:25:30.777 回答