我想使用 CMAttitude 来了解与 iPad/iPhone 屏幕玻璃垂直的矢量(相对于地面)。因此,我会得到如下向量:
请注意,这与方向不同,因为我不关心设备如何围绕 z 轴旋转。因此,如果我将 iPad 面朝下举过头顶,它会显示 (0,-1,0),即使我在头顶上方旋转它(就像直升机一样),它也会继续显示 (0,- 1,0):
我觉得这可能很容易,但由于我是四元数的新手并且不完全理解设备运动的参考框架选项,所以它整天都在逃避我。
我想使用 CMAttitude 来了解与 iPad/iPhone 屏幕玻璃垂直的矢量(相对于地面)。因此,我会得到如下向量:
请注意,这与方向不同,因为我不关心设备如何围绕 z 轴旋转。因此,如果我将 iPad 面朝下举过头顶,它会显示 (0,-1,0),即使我在头顶上方旋转它(就像直升机一样),它也会继续显示 (0,- 1,0):
我觉得这可能很容易,但由于我是四元数的新手并且不完全理解设备运动的参考框架选项,所以它整天都在逃避我。
把它放在一起,当设备面朝上放在桌子上时,您只需以正确的方式将四元数与法线向量相乘。现在我们需要这种表示旋转的四元数乘法的正确方法:根据旋转向量,这是通过以下方式完成的:
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
但我更喜欢四元数的方式,它可以为你省去所有三角函数的麻烦并且表现更好。
感谢 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]];
}