ロール・ピッチ・ヨーの取得について解説

目次

感謝

 JoyShockLibraryの登場のおかげで、念願だったゲームコントローラーのジャイロをHSP3から取得して使う事ができるようになりました。 Dualshock4だけでなく、DualSense、Joy-Con、Pro コントローラーのジャイロが取得できるようになりました。 Jibb Smart様、おありがとうございます。大変感謝いたしております。

 何かしたかった訳ではないのですが、高度なセンサーを満載したガジェットがあるのに遊べないのはもったいないじゃぁないですか。 使えるようになったので、これから精一杯遊ばせていただきます。

 私だけだとアイデア枯渇しそうなので、出来れば多くの人に触れてもらって何か作り出してほしいものです。 ということで、↓このモジュールを使うとHSP3から手軽にJoyShockLibraryを使えるようになるのでご活用ください。

JSL4HSP3(JoyShockLibrary for HSP3)

ロール・ピッチ・ヨーが必要なので作りました

 JoyShockLibrary のおかげで、HSP3でもゲームコントローラーの傾き姿勢を取得できるようになりました。

 しかし、JslGetMotion命令で取得したクォータニオンを実際にゲームに使おうとすると何かと不便です。 コントローラーの動かし方次第では、蓄積した誤差がものすごく大きくなってしまうことがあり、自動校正で誤差修正されるまでの間はまともな値が取得できません。 またそもそも、ゲームで使いたい入力は、ロールまたは、ピッチ、ヨーのいずれかだけを使うことが大半でしょう。 飛行機や自動車の操縦をするのにクォータニオン(全ての方向の姿勢をあらわした値)だけがあっても不便です。ほしい方向だけを変換して取り出せばいいけども、それには誤差の影響が…。

 一方でJslGetMotion命令は、クォータニオン以外にローカル重力方向も返してくれます。 ローカル重力方向は、ゲームコントローラから見た重力方向を指し示す値です。 クォータニオンとは違い、この値はとても安定していてコントローラーをどのように動かしてもほぼ正確な重力方向を指し示します。 計測誤差もほとんどなく、とても安定した計測結果を出してくれます。

 ローカル重力方向を使って、ロール、ピッチ、ヨーをそれぞれ検出できれば誤差が少ない値が取り出せそうです。 ということで、ローカル重力方向を使ってロール・ピッチ・ヨーの検出を実装してみました。 JSL4HSP3 モジュールのサンプルをご確認ください。 (GetAngleRollGetAnglePitchGetAngleYaw

 このページでは、実装方法についての技術的解説を行います。 技術共有…というより、書いておかないと忘れてしまいそうなので書きます。

計測方法の概要

 ローカル座標系3軸の内、回転中心となる1軸は常に水平向きにある状態で回転すると仮定して計算しています。残りの2軸と重力加速度方向がなす角を計算します。この角度は、ベクトル内積(ドット積)を使って求めます。次にゼロ点調整を行います。コントローラーの向きが0度としているときの2軸のもともとの角度を差し引くと現在のコントローラーの角度を算出することができます。cosで角度を求めるので、0度付近や±180度付近では、符号がどちらかわかりません。これは、2軸のそれぞれの計測結果を比較することで判定します。

 例えばロール角、ローカルZ軸まわりの回転の場合は次のようになります。 まず、ローカルZ軸は常に水平向きであると仮定して計算します。 重力加速度とローカルX軸または、重力加速度とローカルY軸の角度を調べれば、コントローラが何度傾いているかがわかります。

計測誤差が発生する原因

 実際には、回転中心にしたい軸が常に完全な水平を保つことは現実的ではありません。 手に持っているだけで常に少し傾いた状態です。持ち方次第では、垂直になることもあります。 回転中心にしたい軸が垂直に近づけば、角度計算に使う残りの2軸は水平に近づきます。 角度計算に使う2軸が水平になってしまうと、どの方向に向けても重力方向との角度が90度になってしまって、回転角を検出できなくなってしまいます。 角度計算に使う2軸が水平に近づくと、どの方向に回転させても、2軸と重力方向との角度がほぼ90度前後になり、回転角度の計測精度が低下します。 この問題は、回転中心にしたい軸が垂直に近づくにつれて顕著になります。

 一つの例を元に、精度低下の原因を推定してみます。 例えばロール(ローカルZ軸まわりの回転)の場合。 ローカルZ軸が水平から少し傾いていた場合を考えてみます。 ローカルX軸を水平方向を向けた場合、ローカルY軸は垂直から少し傾いた状態になります。 ローカルY軸を水平方向に向けた場合も同様に、ローカルX軸は垂直から少し傾いた状態になります。 このように、回転中心にしたい軸が水平から傾いている場合、垂直方向を向いている軸での角度計測結果は信用できません。ここが原因になっていそうです。 一方で、水平方向に近い方の軸は、精度よく計測できていると考えてよさそうです。

解決方法1

 思いつく対策からやってみます。まずは、平均をとる。

 回転中心にしたい軸が水平であれば、角度計算に使う2軸はそれぞれ同じ角度を返します。 この場合「同じ角度」というのは、回転前の位置を0度とした場合の現在の角度の事を指しています。

 回転中心にしたい軸が傾いていると、角度計算に使う2軸はそれぞれ異なる値を返すようになります。 回転角度によっては同じ値を返すこともありますが、また別の角度では大きく異なる値を返す場合もあります。

 そこで、平均をとってしまえば、現在のおおよその角度は知ることができます。 しかし、0度や90度など2軸どちらかの計測精度が悪くなる角度に近づくほど平均角度では検出精度が落ちます。また、0度丁度や90度丁度に合わせてみると、0度丁度や90度丁度を避けるように数値が飛びます。 完全に誤差に引っ張られてしまっています。

 ロール(ローカルZ軸まわりの回転)の場合に、角度計算に使う2軸ローカルX軸とローカルY軸それぞれから算出されるコントローラーの角度の概念をグラフにして描いてみました。概念図なので数値は正確ではありません。 横軸にロール角、縦軸に検出した角度を描いています。 Z軸を水平から少し傾けた状態を想定しています。0度や90度付近では、このような感じで計測精度が悪くなります。

 この状態で2つの値の平均をとっても、精度がよくないばかりか、90度おきに数値が飛んでしまいます。 誤差に強く引っ張られてしまって使い物にならないことは、容易に想像できますね。

解決方法2

 ローカル2軸は、どちらかの精度が悪い時は、どちらかの精度がいいという関係になっています。 重みづけを行い、どちらか精度がいい方の数値を優先的に使って平均をとるようにしてみます。

(θx+θy)/2

 平均をとることで、現在のおおよその角度がわかるようになりました。 あ。θxは、ローカルX軸から検出した現在角度。θyは、ローカルY軸から検出した現在角度です。

 精度が悪くなる角度は、周期的に出現します。また、重みづけの変化は滑らかにしたいのでcosを使ってみます。

cos((θx+θy)/2)

 精度が悪くなるのは、180度周期なので、角度を2倍します。

cos(θx+θy)

 重みづけなので、範囲は0~1にしたいです。

(cos(θx+θy) + 1) / 2

 それらしく適当に作った曲線ですが、動かしてみるとかなり精度がよくなったと感じます。 0度や90度など精度が悪くなる角度で、数値が飛んでいた振れ幅が小さくなりました。 しかし、数値が飛んでしまうのは困ります。

解決方法3

 数値が飛んでしまう原因は、精度が悪い方の重みづけがまだ大きすぎるためです。 またこの問題は、水平と仮定している軸の傾きが大きくなると悪化します。 そこで、重みづけに、水平と仮定している軸の傾きを考慮してみます。

 水平と仮定している軸の傾きが大きい場合は、精度がよくない方の数値をこれまで以上により使わないようにすればよさそうです。 重みづけのcos曲線の山が尖りすぎているので、山の頭を平らにできればよさそうな気がします。

 矩形波…は、使いにくそうなので使わないようにします。cosに入れる角度に重みづけをすることにしました。 0度や90度付近では、0度や90度に近くなり、その他の角度では元の値に近いままになるようにしたい。というわけでsinを使ってみることにしました。

-sin((θx+θy)*2)

 水平と仮定している軸がきちんと水平方向の場合は、補正をしたくありません。 また、水平と仮定している軸の傾きが大きい程、補正値も大きくしたい。 そこで、振幅をcos(θz)で調整することにします。

-sin((θx+θy)*2) * abs(cos(θz))

 これを先ほどの重みづけの式に組み込みます。

(cos(θx+θy -sin((θx+θy)*2) * abs(cos(θz)) ) + 1) / 2

 解決方法2の重みづけと、この式重みづけを比較してみます。また概念グラフです。

 図は、水平と仮定している軸の傾き角度による違いを見やすくするため、正確な角度が検出できていると仮定して計算してあります。 説明には、きれいなグラフの方がわかりやすいと思うので。

 水平と仮定している軸(Zと表記)が水平(90deg)である場合は、解決方法2の重みづけ(水色)と一致しています。 水平と仮定している軸が傾くほどcos曲線の山の頭がつぶれているので、精度が高い方の角度を優先する範囲が増えています。

 では次に、0度や90度付近で精度が落ちているローカルX軸、ローカルY軸の値を使って、重みづけを計算した場合も作図してみます。

 水色の線は、解決方法2の方法つまり平均角度を使って重みづけを行った場合です。この方法では、0度や90度付近でウェイトが1または0になり切れていません。これでは精度が悪い方の影響が出てきてしまいます。 一方で、赤色の線。解決方法3の水平と仮定している軸の傾きを考慮した方は、0度や90度付近でウェイトが1または0になっています。これなら精度が悪い方の影響をなくすことができます。

 この補正値を使って、ローカルX軸、ローカルY軸が取得した角度から現在のコントローラーの角度を推定して、計測誤差を計算してみました。 図は、コントローラーをピッチ方向に少し傾けてロール角の角度検出を行い、設定値と計測地の誤差を推定してグラフ化したものです。

 誤差が完全になくなるわけではありませんが、解決方法3の水平と仮定している軸を考慮した重みづけが有効であることがわかります。

 実際に使用してみると、この補正方法はかなり有効であると感じることができます。 論理的に求めた補正方法ではなく、行き当たりばったりの適当補正ですが、十分実用範囲内だと思います。

運用について

 これで水平と仮定している軸が傾いていてもある程度計測できますが、ものには限度というものがあります。 コントローラーの持ち方によってはうまく値を取得することができません。 プレイヤーに計測精度がよくなる持ち方をさせる工夫をすることが重要です。 ここでは、どのような持ち方が適切かについて記述しておこうと思います。

ロール(Z軸中心の回転)

 Joy-Con片手持ちの場合、長手方向を水平に構えて持ってください。 長手方向を軸に左右に回転させるように動かしてください。焼き鳥をひっくり返すような動きです。

ピッチ(X軸中心の回転)

 Joy-Con片手持ちの場合、長手方向を傾けるように動かしてください。 長手方向を水平や垂直にすると反応します。剣の柄を持つイメージ。剣を振り下ろすように動かしてください。

ヨー(Y軸中心の回転)

 Joy-Con両手持ちまたはDS4の場合、両手で持ってトリガー以外のボタンが配置されている面を顔の方に向けて構えてください。テーブルなど水平な場所に置いたり、前に倒して持つとうまく読み取れません。できるだけ手前に引き起こす形で持つようにしてください。車のハンドルのような角度で持って動かすイメージです。 角度検出に重力を使うので、水平な場所に置いてしまうと検出できません。

最後に

 先にグラフの形をイメージしてから、つじつまがあう計算式をあてはめる形で作り上げてみました。 数学的意味もなにもあったものではありませんが、かなり実用的なものができたと思っています。

 なお、このページで使用しているグラフは全て概念図です。それなりに計算して作ったものですが、精度が高いわけではありません。 なにとぞご了承ください。

 計算部分をJSL4HSP3モジュールの関数にしてあります。そのまま使えば何も考えなくてもロール・ピッチ・ヨーを取得できます。説明を読んで自分で実装するよりも、このモジュールをそのままお使いください。

 これでジャイロを使ったゲームやツールや便利な道具がもっともっと出てきてくれるといいですね。

改訂履歴

  • 2023/07/16 : 初版
  • 2023/11/08 : 分かりにくい文章を修正して読みやすくした。ゲームコントローラの図を追加。

関連記事

  1. JSL4HSP3 前の画像 次の画像 ダウンロード JSL4HSP3 Ver....
  2. 加速度センサーからの取得値の処理 加速度センサーとこれまでのボタンの違い SIXAXISについ...