iPhone/iPad を縦向きに、画面を手前にして正面に垂直に持っているとします。画面を自分に向けたまま、デバイスを片側に傾けます。CMMotionManager を使用してその静的傾斜角をどのように測定しますか? 簡単な答えがあるはずの簡単な質問のようですが、四元数と回転行列に消えない方法は見つかりません。
誰かが私に実際の例を教えてもらえますか?
iPhone/iPad を縦向きに、画面を手前にして正面に垂直に持っているとします。画面を自分に向けたまま、デバイスを片側に傾けます。CMMotionManager を使用してその静的傾斜角をどのように測定しますか? 簡単な答えがあるはずの簡単な質問のようですが、四元数と回転行列に消えない方法は見つかりません。
誰かが私に実際の例を教えてもらえますか?
見てください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 に近い場所です。
この投稿を見て、このベクトルを角度に変換したい場合は、次のアルゴリズムを使用できることに気付きました。
デバイスが垂直から何度回転したかを計算する場合 (正は時計回り、負は反時計回り)、次のように計算できます。
// 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
transform
これを使用して、Quartz 2Dプロパティを介してビューをどれだけ回転させるかを把握できます。
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;
遅い回答のようなものですが、githubとそれに付随するブログ記事で実際の例を見つけることができます。
上記の記事を要約すると、クォータニオンを使用すると、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
// ...
self.horizon
これは、UIViewを回転させて、デバイスを傾けたときに水平線と同じ高さを維持する例です。
- (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 のインスタンスが 1 つしかないことを確認して、これを事前に初期化し、プロパティ経由でアクセスする必要があります。