查找iOS设备的法线向量

时间:2021-06-01 00:01:50

I would like to use CMAttitude to know the vector normal to the glass of the iPad/iPhone's screen (relative to the ground). As such, I would get vectors like the following:

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

查找iOS设备的法线向量

Notice that this is different from orientation, in that I don't care how the device is rotated about the z axis. So if I was holding the iPad above my head facing down, it would read (0,-1,0), and even as I spun it around above my head (like a helicopter), it would continue to read (0,-1,0):

请注意,这与方向不同,因为我不关心设备如何围绕z轴旋转。因此,如果我把iPad放在我的头顶朝下,它会读取(0,-1,0),即使我将它旋转到我头顶上方(如直升机),它仍将继续读取(0, - 1,0):

查找iOS设备的法线向量

I feel like this might be pretty easy, but as I am new to quaternions and don't fully understand the reference frame options for device motion, its been evading me all day.

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

2 个解决方案

#1


22  

  1. In your case we can say rotation of the device is equal to rotation of the device normal (rotation around the normal itself is just ignored like you specified it)
  2. 在你的情况下,我们可以说设备的旋转等于设备法线的旋转(围绕法线本身的旋转就像你指定的那样被忽略)
  3. CMAttitude which you can get via CMMotionManager.deviceMotion provides the rotation relative to a reference frame. Its properties quaternion, roation matrix and Euler angles are just different representations.
  4. 您可以通过CMMotionManager.deviceMotion获取的CMAttitude提供相对于参考框架的旋转。它的属性四元数,旋转矩阵和欧拉角只是不同的表征。
  5. The reference frame can be specified when you start device motion updates using CMMotionManager's startDeviceMotionUpdatesUsingReferenceFrame method. Until iOS 4 you had to use multiplyByInverseOfAttitude
  6. 使用CMMotionManager的startDeviceMotionUpdatesUsingReferenceFrame方法启动设备运动更新时,可以指定参考框架。在iOS 4之前,你必须使用multiplyByInverseOfAttitude

Putting this together you just have to multiply the quaternion in the right way with the normal vector when the device lies face up on the table. Now we need this right way of quaternion multiplication that represents a rotation: According to Rotating vectors this is done by:

将这些放在一起,当设备面朝上放在桌子上时,你必须以正确的方式将四元数与法线向量相乘。现在我们需要这种表示旋转的四元数乘法的正确方法:根据旋转向量,这可以通过以下方式完成:

n = q * e * q' where q is the quaternion delivered by CMAttitude [w, (x, y, z)], q' is its conjugate [w, (-x, -y, -z)] and e is the quaternion representation of the face up normal [0, (0, 0, 1)]. Unfortunately Apple's CMQuaternion is struct and thus you need a small helper class.

n = q * e * q'其中q是由CMAttitude [w,(x,y,z)]传递的四元数,q'是它的共轭[w,( - x,-y,-z)],e是面向正常[4,(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

Quaternion.h:

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.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;
}

I know quaternions are a bit weird at the beginning but once you have got an idea they are really brilliant. It helped me to imagine a quaternion as a rotation around the vector (x, y, z) and w is (cosine of) the angle.

我知道四元数在开始时有点奇怪,但是一旦你有了一个想法它们真的很棒。它帮助我将四元数想象为围绕向量(x,y,z)的旋转,并且w是角度的(余弦)。

If you need to do more with them take a look at cocoamath open source project. The classes Quaternion and its extension QuaternionOperations are a good starting point.

如果您需要对它们进行更多操作,请查看cocoamath开源项目。 Quaternion类及其扩展QuaternionOperations是一个很好的起点。

For the sake of completeness, yes you can do it with matrix multiplication as well:

为了完整起见,是的,您也可以使用矩阵乘法:

n = M * e

n = M * e

But I would prefer the quaternion way it saves you all the trigonometric hassle and performs better.

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

#2


3  

Thanks to Kay for the starting point on the solution. Here is my implementation for anyone that needs it. I made a couple of small tweeks to Kay's advice for my situation. As a heads up, I'm using a landscape only presentation. I have code that updates a variable _isLandscapeLeft to make the necessary adjustment to the direction of the vector.

感谢凯为解决方案的起点。以下是我需要它的人的实现。我根据凯的建议为我的情况做了一些小小的调整。作为一个抬头,我正在使用仅景观演示。我有更新变量_isLandscapeLeft的代码,以对向量的方向进行必要的调整。

Quaternion.h

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

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

And my game class that uses the quaternion for shooting:

我的游戏类使用四元数进行拍摄:

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

#1


22  

  1. In your case we can say rotation of the device is equal to rotation of the device normal (rotation around the normal itself is just ignored like you specified it)
  2. 在你的情况下,我们可以说设备的旋转等于设备法线的旋转(围绕法线本身的旋转就像你指定的那样被忽略)
  3. CMAttitude which you can get via CMMotionManager.deviceMotion provides the rotation relative to a reference frame. Its properties quaternion, roation matrix and Euler angles are just different representations.
  4. 您可以通过CMMotionManager.deviceMotion获取的CMAttitude提供相对于参考框架的旋转。它的属性四元数,旋转矩阵和欧拉角只是不同的表征。
  5. The reference frame can be specified when you start device motion updates using CMMotionManager's startDeviceMotionUpdatesUsingReferenceFrame method. Until iOS 4 you had to use multiplyByInverseOfAttitude
  6. 使用CMMotionManager的startDeviceMotionUpdatesUsingReferenceFrame方法启动设备运动更新时,可以指定参考框架。在iOS 4之前,你必须使用multiplyByInverseOfAttitude

Putting this together you just have to multiply the quaternion in the right way with the normal vector when the device lies face up on the table. Now we need this right way of quaternion multiplication that represents a rotation: According to Rotating vectors this is done by:

将这些放在一起,当设备面朝上放在桌子上时,你必须以正确的方式将四元数与法线向量相乘。现在我们需要这种表示旋转的四元数乘法的正确方法:根据旋转向量,这可以通过以下方式完成:

n = q * e * q' where q is the quaternion delivered by CMAttitude [w, (x, y, z)], q' is its conjugate [w, (-x, -y, -z)] and e is the quaternion representation of the face up normal [0, (0, 0, 1)]. Unfortunately Apple's CMQuaternion is struct and thus you need a small helper class.

n = q * e * q'其中q是由CMAttitude [w,(x,y,z)]传递的四元数,q'是它的共轭[w,( - x,-y,-z)],e是面向正常[4,(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

Quaternion.h:

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.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;
}

I know quaternions are a bit weird at the beginning but once you have got an idea they are really brilliant. It helped me to imagine a quaternion as a rotation around the vector (x, y, z) and w is (cosine of) the angle.

我知道四元数在开始时有点奇怪,但是一旦你有了一个想法它们真的很棒。它帮助我将四元数想象为围绕向量(x,y,z)的旋转,并且w是角度的(余弦)。

If you need to do more with them take a look at cocoamath open source project. The classes Quaternion and its extension QuaternionOperations are a good starting point.

如果您需要对它们进行更多操作,请查看cocoamath开源项目。 Quaternion类及其扩展QuaternionOperations是一个很好的起点。

For the sake of completeness, yes you can do it with matrix multiplication as well:

为了完整起见,是的,您也可以使用矩阵乘法:

n = M * e

n = M * e

But I would prefer the quaternion way it saves you all the trigonometric hassle and performs better.

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

#2


3  

Thanks to Kay for the starting point on the solution. Here is my implementation for anyone that needs it. I made a couple of small tweeks to Kay's advice for my situation. As a heads up, I'm using a landscape only presentation. I have code that updates a variable _isLandscapeLeft to make the necessary adjustment to the direction of the vector.

感谢凯为解决方案的起点。以下是我需要它的人的实现。我根据凯的建议为我的情况做了一些小小的调整。作为一个抬头,我正在使用仅景观演示。我有更新变量_isLandscapeLeft的代码,以对向量的方向进行必要的调整。

Quaternion.h

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

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

And my game class that uses the quaternion for shooting:

我的游戏类使用四元数进行拍摄:

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