ios - Medición del ángulo de inclinación con CMMotionManager
(4)
Supongamos que está sosteniendo un iphone / ipad verticalmente frente a usted con la pantalla hacia usted, en orientación vertical. Inclina el dispositivo hacia un lado, manteniendo la pantalla hacia usted. ¿Cómo mides ese ángulo de inclinación estático usando CMMotionManager? Parece una pregunta simple que debería tener una respuesta simple, pero no puedo encontrar ningún método que no desaparezca en cuaterniones y matrices de rotación.
¿Alguien puede señalarme un ejemplo trabajado?
Aquí hay un ejemplo que rota un self.horizon de self.horizon
para mantenerlo nivelado con el horizonte a medida que inclina el dispositivo.
- (void)startDeviceMotionUpdates
{
CMMotionManager* coreMotionManager = [[CMMotionManager alloc] init];
NSOperationQueue* motionQueue = [[NSOperationQueue alloc] init]
CGFloat updateInterval = 1/60.0;
CMAttitudeReferenceFrame frame = CMAttitudeReferenceFrameXArbitraryCorrectedZVertical;
[coreMotionManager setDeviceMotionUpdateInterval:updateInterval];
[coreMotionManager startDeviceMotionUpdatesUsingReferenceFrame:frame
toQueue:motionQueue
withHandler:
^(CMDeviceMotion* motion, NSError* error){
CGFloat angle = atan2( motion.gravity.x, motion.gravity.y );
CGAffineTransform transform = CGAffineTransformMakeRotation(angle);
self.horizon.transform = transform;
}];
}
Esto es un poco simplificado: debe asegurarse de tener solo una instancia de CMMotionManager en su aplicación, por lo que desea preinicializar esto y acceder a él a través de una propiedad.
Dado que iOS8 CoreMotion también le devuelve un objeto CMAttitude
, que contiene propiedades de pitch
, roll
y yaw
, así como el cuaternión. Usar esto significa que no tiene que hacer las operaciones matemáticas manuales para convertir la aceleración en orientación.
Es una especie de respuesta tardía, pero puede encontrar un ejemplo de trabajo en github y el artículo de blog que viene con él.
Para resumir el artículo mencionado anteriormente, puede usar cuaterniones para evitar el problema de bloqueo de cardán que probablemente enfrentará cuando sostenga el iPhone verticalmente.
Aquí está la parte de codificación que calcula la inclinación (o guiñada):
CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
double yaw = asin(2*(quat.x*quat.z - quat.w*quat.y));
// use the yaw value
// ...
Incluso puede agregar un filtro de Kalman simple para facilitar la orientación:
CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
double yaw = asin(2*(quat.x*quat.z - quat.w*quat.y));
if (self.motionLastYaw == 0) {
self.motionLastYaw = yaw;
}
// kalman filtering
static float q = 0.1; // process noise
static float r = 0.1; // sensor noise
static float p = 0.1; // estimated error
static float k = 0.5; // kalman filter gain
float x = self.motionLastYaw;
p = p + q;
k = p / (p + r);
x = x + k*(yaw - x);
p = (1 - k)*p;
self.motionLastYaw = x;
// use the x value as the "updated and smooth" yaw
// ...
Mira la gravity
self.deviceQueue = [[NSOperationQueue alloc] init];
self.motionManager = [[CMMotionManager alloc] init];
self.motionManager.deviceMotionUpdateInterval = 5.0 / 60.0;
// UIDevice *device = [UIDevice currentDevice];
[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryZVertical
toQueue:self.deviceQueue
withHandler:^(CMDeviceMotion *motion, NSError *error)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
CGFloat x = motion.gravity.x;
CGFloat y = motion.gravity.y;
CGFloat z = motion.gravity.z;
}];
}];
Con este marco de referencia ( CMAttitudeReferenceFrameXArbitraryZVertical
), si z
es cercano a cero, lo mantienes en un plano perpendicular al suelo (por ejemplo, como si lo sostienes contra una pared) y al girarlo en ese plano, y
los valores cambian Vertical es donde x
está cerca de cero e y
está cerca de -1.
Al mirar esta post , observo que si quieres convertir este vector en ángulos, puedes usar los siguientes algoritmos.
Si desea calcular cuántos grados de vertical se rota el dispositivo (donde el positivo es en el sentido de las agujas del reloj, el negativo en el sentido contrario al de las agujas del reloj), puede calcularlo como sigue:
// how much is it rotated around the z axis
CGFloat angle = atan2(y, x) + M_PI_2; // in radians
CGFloat angleDegrees = angle * 180.0f / M_PI; // in degrees
Puede usar esto para calcular cuánto girar la vista a través de la propiedad de transform
2D de cuarzo:
self.view.layer.transform = CATransform3DRotate(CATransform3DIdentity, -rotateRadians, 0, 0, 1);
(Personalmente, actualizo el ángulo de rotación en el método startDeviceMotionUpdates
y actualizo esta transform
en CADisplayLink
, que desacopla las actualizaciones de la pantalla de las actualizaciones de ángulo).
Puedes ver hasta dónde lo has inclinado hacia atrás / adelante a través de:
// how far it it tilted forward and backward
CGFloat r = sqrtf(x*x + y*y + z*z);
CGFloat tiltForwardBackward = acosf(z/r) * 180.0f / M_PI - 90.0f;