toio.jsのモーションセンサー(姿勢角検出とシェイク検出)を使って画面上のルービックキューブを操作する

## はじめに

昨年は[toioのDo!コン](https://protopedia.net/event/13)に参加させて頂きました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/Do%E3%82%B3%E3%83%B3?src=hash&amp;ref_src=twsrc%5Etfw">#Doコン</a> の応募作品を <a href="https://twitter.com/hashtag/toioDo?src=hash&amp;ref_src=twsrc%5Etfw">#toioDo</a> のサンプルとして掲載して頂きました。ありがとうございます！<br><br> <a href="https://t.co/ThObMUy2Tf">https://t.co/ThObMUy2Tf</a></p>&mdash; devdelights (@devdelights_) <a href="https://twitter.com/devdelights_/status/1407014010575147009?ref_src=twsrc%5Etfw">June 21, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

こんな形でサンプルとして紹介して頂けたり、関連のYouTube配信等でも取り上げて頂き、楽しませて頂きました。

このコンテストは応募要件として、Scratchによる実装が必須でしたが、事前にアイデア審査があり、そちらでは[toio.js](https://github.com/toio/toio.js)を使ってデモ動画を作成し実現性をアピールしていました[^1]。

## toio.js

toioのキューブをBLE経由で制御するTypeScriptで書かれたライブラリです。型チェックが効くため効率的にプロトタイプができます。実際、上記コンテストのアイデア応募段階でも、キューブの最新の技術仕様[^2]を参考に、

- 簡易カードの読み取り ([53d429bab](https://github.com/devdelights/toio.js/commit/53d429bab95d98769b23c897c8dad33107243756))
- 加速度指定付きモーター制御 ([70dea04](https://github.com/devdelights/toio.js/commit/70dea04b8823ede782329216b6c86dcc68b995f2))

等をローカルで追加した上で、何か面白い動きはできないかと模索していました。

toioのプログラミングを取り巻く環境としては、キューブのBLEのCharacteristicの仕様が公開されており、toio.jsを使わなくとも直接コントロールできてしまうのと、[toio SDK for Unity](https://morikatron.com/t4u/)の方が積極的にメンテナンスされている印象で、実際toio.jsの更新も1年以上止まっているようです。

[^1]: Do!コンアイデア応募動画 - https://youtu.be/N8ly1ICyyJo
[^2]: toio™コア キューブ 技術仕様 - https://toio.github.io/toio-spec/

## toio.jsにローカルで機能追加

スライドパズルからパズルつながりで、ルービックキューブにも興味を持ったのと、ローカルで修正を加えたtoio.jsの環境があったので

- **「toioキューブ2台を両手で持って画面上のパズルを解く」** 

という体験を目指し、作りっぱなしの物がありました。今回Qiita初投稿という形で改めて整理してみます。

#### 変更点1：Web Bluetooth 経由の2台接続

toio.jsはブラウザでも動作します[^3]。以下の素晴らしい記事を参考にさせて頂きました。

- [toio.jsをブラウザで動かしてみた](https://qiita.com/shinsuke-noguchi/items/037d729327232b6c0d33)

ただ手元で試した限り2台接続には失敗しました。症状としてはこちらの問題([#318](https://github.com/noble/noble/issues/358))と同じで、内部で利用しているnobleの実装が原因で、BLEのAdvertisingの再Scanが走らないようでした。SDKのAPIを無理やり変更して回避します。

https://github.com/devdelights/toio.js/commit/0a01a0266e71e020d0ecccf9db681a2a1b70588c

[^3]: 試せていないですが、[p5.toio](https://qiita.com/tetunori_lego/items/23642e50129d934876e5) もあるようです。

#### 変更点2：姿勢角検出(モーションセンサー)

ルービックキューブの各面を回転させる操作と回し心地(!?)は、是非toioのセンサーで実現したいものです。toioから取得できるセンサーの角度の詳細はこちらの記事が参考になります。

- [ #toioの姿勢角（ロール・ピッチ・ヨー）をブラウザ上で可視化（Web Bluetooth API、Smoothie Charts を利用）](https://qiita.com/youtoy/items/9b3b39b3f91b36821305)

が、いざ試してみようとすると、toio.jsでは未実装。こんな感じで足してみました。

https://github.com/devdelights/toio.js/commit/202bbfd93800b14ccdf20c9cef3b7d3736cdcd96

あとはWebのUIと繋げるだけです[^4]。注意点は座標系の向きです。Webの場合CSSの座標は左手系で画面から向かってくる方向がZ軸の正になります。一方toioのスペックでは右手系でキューブの底に向かってZ軸が正です。

## モーションセンサーのみでルービックキューブを操作

以上で最低限の準備は揃いました。早速2台のキューブを使って、1台には視点操作、もう1台には回転操作を割り当てて動作を見てみます。

6面あるルービックキューブの面から回転させる面を絞り込むのが難しいため、**3面に限定して動作をみてみた様子**がこちらです。

![output2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2411898/65dd7458-295c-0e91-7f48-ff2f72928b6e.gif)


toioの姿勢角の推定値そのものは応答もよく精度は高そうです。が、姿勢角の値から回転方向への変換がときどき失敗したり、手でキューブを操作しているうちに徐々に回転させる軸がずれたりで、実用的な感じにはならなそうでした。そこで、他の入力(ボタンやシェイク検出)も合わせた操作も検討します。

[^4]: https://codepen.io/Omelyan/pen/BKmedK を利用させて頂きました。

#### 変更点3：シェイク検出

キューブを振る動作が数値として取得できます。アプリで使い易い印象です。先ほどの姿勢角の変更と同様の実装をtoio.jsに追加します。

https://github.com/devdelights/toio.js/commit/a41aef65d8256a9b6c19d586c2b97f5dcbe1ced6

こちらも先ほどと同様にWebBluetoothでコントロールする記事がありました。

- [#toio のシェイク検出の情報を Web Bluetooth API で受け取り ](https://qiita.com/youtoy/items/82cf0e90e11bf68c0507)

## 姿勢角検出とシェイク検出でルービックキューブを操作

#### 候補面の選択

回転させる面を選ぶ良い方法が思い浮かばなかったので、明示的に選択することにしました。シェイク検出を使ってモードを切り替えるイメージで、予め回転させる面を指定します。

![selectedface.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2411898/be4abc62-c727-d386-edd2-2f04291dde3f.gif)

またモーションセンサーの結果が思い通りに取れなくても、ボタンを使って回転させられるようにもしてみました。

![button.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2411898/4672900e-0fd6-3fa9-8c85-61eb82185ac3.gif)

最終的な操作のアイデアは以下です。

 - 1台目のキューブ(視点操作とモード切り替え)

|入力|取得できる値 |操作での利用方法 |
|-|-|-|
|**モーションセンサ**|姿勢角|画面上のルービックキューブの回転|
|**シェイク検出**|シェイクされた大きさのレベル|候補面選択モードとの切り替え|
|ボタン(予備)| 長押し |押下時のみ候補面選択モードを表示 |

 - 2台目のキューブ(ルービックキューブの面の回転)

|入力|取得できる値 |操作での利用方法 |
|-|-|-|
|**モーションセンサ**|姿勢角|選択された面の回転|
|**シェイク検出**|シェイクされた大きさのレベル|回転される候補面の切り替え|
|ボタン(予備)|シングルクリック |選択された面を時計回りに回転 |
| |ダブルクリック |選択された面を反時計回りに回転 |

toio.jsの機能をフル活用して操作はできるようになりましたが、やはり姿勢角のみでキューブを操作したいところです[^5]。

[^5]: 角度の時間的な変化からキューブの操作へのマッピングの辺りにもう一工夫したかったです。

## 最後に

今回は操作部分のみでしたが、ルービックキューブのデータ構造とか解法等、興味深い話題が多くまだまだ勉強が必要なようです。今年もtoioと並行して学びつつ、再度どこかを掘り下げてみたいと思っています。