Измерение угла наклона с помощью CMMotionManager
Предположим, что вы держите iphone/ipad вертикально перед собой с экраном, обращенным к вам, в портретной ориентации. Вы наклоняете устройство в сторону, удерживая экран перед собой. Как вы измеряете этот статический угол наклона с помощью CMMotionManager? Кажется, простой вопрос, который должен иметь простой ответ, но я не могу найти какой-либо метод, который не исчезнет в кватернионы и матрицы вращения.
Может ли кто-нибудь указать мне на обработанный пример?
Ответы
Ответ 1
Посмотрите 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;
}];
}];
С этим опорным кадром (CMAttitudeReferenceFrameXArbitraryZVertical
), если z
близок к нулю, вы держите его на плоскости, перпендикулярной земле (например, как будто вы держите ее у стены), и когда вы вращаете ее на этой плоскости изменяются значения x
и y
. Вертикаль - это где x
близок к нулю, а y
близок к -1.
Глядя на это post, я замечаю, что если вы хотите преобразовать этот вектор в углы, вы можете использовать следующие алгоритмы.
Если вы хотите рассчитать, сколько градусов от вертикали устройство повернуто (где положительный по часовой стрелке, отрицательный против часовой стрелки), вы можете рассчитать это как:
// 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
Вы можете использовать это, чтобы выяснить, сколько вращать представление с помощью свойства Quartz 2D transform
:
self.view.layer.transform = CATransform3DRotate(CATransform3DIdentity, -rotateRadians, 0, 0, 1);
(Лично я обновляю угол поворота в методе startDeviceMotionUpdates
и обновляю этот transform
в CADisplayLink
, который отделяет обновления экрана от обновлений угла.)
Вы можете видеть, как далеко вы наклонили его назад/вперед с помощью:
// 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;
Ответ 2
Это своего рода поздний ответ, но вы можете найти рабочий пример для github и статьи блога, который приходит с ним.
Чтобы подвести итог упомянутой выше статье, вы можете использовать кватернионы, чтобы избежать проблемы с блокировкой gimbal , с которой вы, вероятно, сталкиваетесь при вертикальном удерживании iPhone.
Вот часть кодирования, которая вычисляет угол наклона (или рыскания):
CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
double yaw = asin(2*(quat.x*quat.z - quat.w*quat.y));
// use the yaw value
// ...
Вы можете даже добавить простой фильтр Калмана, чтобы облегчить рыскание:
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
// ...
Ответ 3
Вот пример, который вращает UIView self.horizon
, чтобы поддерживать его на уровне горизонта, когда вы наклоняете устройство.
- (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;
}];
}
Это немного упрощено - вы должны быть уверены, что в вашем приложении есть только один экземпляр CMMotionManager, поэтому вы хотите предварительно инициализировать это и получить к нему доступ через свойство.
Ответ 4
Так как iOS8 CoreMotion также возвращает вам объект CMAttitude
, который содержит свойства pitch
, roll
и yaw
, а также кватернион. Использование этого будет означать, что вам не нужно выполнять ручную математику, чтобы преобразовать ускорение в ориентацию.