Encontrar vectores normales para dispositivos iOS
gyroscope core-motion (2)
Me gustaría usar CMAttitude para conocer el vector normal al cristal de la pantalla del iPad / iPhone (en relación con el suelo). Como tal, obtendría vectores como los siguientes:
Observe que esto es diferente de la orientación, ya que no me importa cómo se gire el dispositivo con respecto al eje z. Entonces, si estuviera colocando el iPad sobre mi cabeza hacia abajo, leería (0, -1,0), e incluso mientras lo giraba sobre mi cabeza (como un helicóptero), continuaría leyendo (0, - 1,0):
Siento que esto podría ser bastante fácil, pero como soy nuevo en cuaterniones y no entiendo completamente las opciones de marco de referencia para el movimiento del dispositivo, me ha estado evitando todo el día.
- En su caso, podemos decir que la rotación del dispositivo es igual a la rotación del dispositivo normal (la rotación alrededor de la normal misma se ignora como usted lo especificó)
- CMAttitude que puede obtener a través de CMMotionManager.deviceMotion proporciona la rotación relativa a un marco de referencia . Sus propiedades quaternion, roation matrix y los ángulos de Euler son representaciones diferentes.
- El marco de referencia se puede especificar cuando inicia las actualizaciones de movimiento del dispositivo utilizando el método startDeviceMotionUpdatesUsingReferenceFrame de startDeviceMotionUpdatesUsingReferenceFrame . Hasta iOS 4 tenías que usar multiplyByInverseOfAttitude
Al juntar esto, solo tienes que multiplicar el cuaternión de la manera correcta con el vector normal cuando el dispositivo se encuentra boca arriba sobre la mesa. Ahora necesitamos esta forma correcta de multiplicación de cuaterniones que represente una rotación: de acuerdo con los vectores de rotación, esto se realiza mediante:
n = q * e * q '' donde q es el cuaternión entregado por CMAttitude [w, (x, y, z)], q'' es su conjugado [w, (-x, -y, -z)] y e es la representación de cuaternión de la cara arriba normal [0, (0, 0, 1)]. Desafortunadamente, el CMQuaternion de Apple está estructurado y, por lo tanto, necesita una pequeña clase de ayuda.
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
Cuaternion.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;
Cuaternion.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;
}
Sé que los cuaterniones son un poco raros al principio, pero una vez que tienes una idea, son realmente brillantes. Me ayudó a imaginar un cuaternión como una rotación alrededor del vector (x, y, z) y w es (coseno de) el ángulo.
Si necesita hacer más con ellos, eche un vistazo al proyecto de código abierto de cocoamath . Las clases Quaternion y su extensión QuaternionOperations son un buen punto de partida.
Para completar, sí, también puede hacerlo con la multiplicación de matrices:
n = M * e
Pero preferiría la forma en que cuaternión le ahorra todos los problemas trigonométricos y se desempeña mejor.
Gracias a Kay por el punto de partida de la solución. Aquí está mi implementación para cualquiera que lo necesite. Hice un par de pequeños ajustes al consejo de Kay para mi situación. Como un heads up, estoy usando una presentación solo paisaje. Tengo un código que actualiza una variable _isLandscapeLeft para realizar el ajuste necesario a la dirección del vector.
Cuaternion.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
Cuaternion.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
Y mi clase de juego que usa el cuaternión para disparar:
-(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]];
}