スノーボードと学ぶCoreMotion

こんにちは！
[ZOZOテクノロジーズ #3 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/zozo_tech3) 11日目の記事を担当します。
よろしくお願いします。

昨日10日目は、@YasuhiroKimesawaさんによる
[管理画面はもういらない!？SlackとChatbotを連携する](https://qiita.com/YasuhiroKimesawa/items/363518b693dc1cd9fe98)
でした！:santa:

まだ読んでいない方は、是非読んでみてください:snowflake:

#今回は

前回に引き続き、冬っぽいテーマを探しました。
そこで、傾きによって光るスノーボードを作ってみたいなと漠然と思っておりましたので

iPhoneを使用して、簡単に傾きの角度が取得できないか調べてみました。

その中で、CoreMotion[^1]を使うことで、簡単に傾きが取れることがわかりましたので、概要をまとめることとしました。

#CoreMotionについて
iPhoneには加速度センサ、ジャイロセンサ...などといった様々なセンサーが搭載されています。
CoreMotion[^2]は、これらのセンサーからの情報にアクセスして、アプリでの使用を可能とするフレームワークです。

##CMAttitude
今回はその中でもiPhone端末の傾きから、左右の傾き情報が取得可能な、CMAttiudeクラスを使って見みました。
ここでは、pitch、roll、yawの3つの端末の傾きの角度を取得することができます。

取得できる角度を、図でまとめてみました。

<img width="600" alt="![4.png]" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368129/f642dcb9-abff-c182-6f5a-84bee712c527.png">

###①pitch
図のx軸方向を中心軸とした時の、端末の傾きの角度

###②roll
図のy軸方向を中心軸とした時の、端末の傾きの角度

###③yaw
図のz軸方向を中心軸とした時の、端末の傾きの角度

今回は、この3つのうち①のpitchを使って、傾きの取得にのぞみました。

#実際の計測方法を考える
iPhoneを使って、端末から傾きの情報を取得できることがわかりましたので、実際に計測する方法を考えました。

##試みその1
最初はスノーボードの板に対して、垂直にiPhoneを立てて計測する方法を考えました。
この方法は、バインディングの背の部分に端末を貼り付けて、計測を行うことで角度を取得できないかと試みたものです。

<img width="600" alt="1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368129/967992f5-b0fe-0b84-4302-27b12de3e014.png">

しかしこの試みは、次の問題点によって見送ることにしました。

###基準角度の考慮漏れ
この試みでは端末が地面に対して、垂直に立った状態を0度として、そこからpitchの値を参照して、何度傾いたかを取得しようと考えていました。

しかし、実際に使ってみましたところ、地面に対して水平になっている状態を0度として扱われるため、当初考えていた方法ですと値を調整して扱う必要があることがわかりました。

計測開始時にCMAttitudeReferenceFrame[^3]にて、基準角度を指定できるようですが、z軸が垂直な状態が前提のものが多い為、見送ることとしました。

##試みその2
端末が地面に対して、水平になっている状態を0度として扱う必要性があることがわかったので、板に付けて計測することとしました。

<img width="600" alt="5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368129/a8a2532e-f314-1473-440d-5f08d88268f0.png">

この方法ですと、端末が下に傾く状態(トゥエッジ)ですと、マイナスの角度が取得できます。
また、端末が上に傾いた状態(ヒールエッジ)は、プラスの角度が取得できます。

これによって、滑っている斜面に対して

- 板をどれぐらいの角度傾けているか
- 前と後ろのどちらに重心がかかっているか

がそれぞれ、判定できるようになります。

#実際に実装してみる
以上を踏まえて、傾きの値を取得、判定をこちらで試してみました。

```swift:
import CoreMotion

class ViewController: UIViewController {
    let motionManager = CMMotionManager()
    let deg = 180/Double.pi
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        motionManager.deviceMotionUpdateInterval = 0.1
        
        motionManager.startDeviceMotionUpdates(to: OperationQueue.current!) { (deviceMotion, error) in
            let pitch = deviceMotion!.attitude.pitch * deg
            
            if pitch == 0 {
                print("フラット")
            } else if pitch < 0 {
                print("トゥエッジ：\(-pitch)度")
            } else {
                print("ヒールエッジ：\(pitch)度")
            }
        }
    }
}
```

##備考

###deviceMotionUpdateInterval
ここでは、端末の傾き情報を取得する間隔を指定しています。
単位は秒となるので、ここでは0.1秒毎に傾き情報が更新されることになります。

###startDeviceMotionUpdates(to:withHandler:)
startDeviceMotionUpdatesを呼び出して、端末の傾き情報の取得を開始します。
情報はhandler内で、deviceMotionUpdateIntervalに設定した間隔で更新されます。

###pitch
CMAttitude.pitchから取得される傾き情報の単位は、ラジアン(rad)になります。
(このままですと、取得した値もパッと見でイメージするのが難しいです)
そのため、以下の公式に当てはめて、度数法(deg)で表現しています。

```math
deg = rad × \frac{180}{π} \\
```

#最後に
傾きは簡単に取れましたが、板を光らせるまでの道のりは、まだまだ遠そうです。
昨シーズンは、暖冬の影響で雪が少なめでしたので、今年はたくさん降ると良いですね。
クリスマスまで、体調に気を付けてお過ごしください:santa:

明日は、@ymd_aaaさんです。よろしくお願いします！:pray:

[^1]: Apple Developer公式ドキュメント：https://developer.apple.com/documentation/coremotion
[^2]: Apple Developer公式ドキュメント：https://developer.apple.com/documentation/coremotion/cmattitude
[^3]: Apple Developer公式ドキュメント：https://developer.apple.com/documentation/coremotion/cmattitudereferenceframe