[個人アプリ開発記]サイコロを操作し,底面を記録するARアプリ

##はじめに
こんにちは。少し前になりますが,3つ目となる[アプリ](https://apps.apple.com/jp/app/arサイコロ/id1532692212)をリリースしましたので,その機能や流れについて書こうと思います。なお一つ目,２つ目についてはそれぞれ[こちら](https://qiita.com/Hyperbolic_____/items/8e8057398bddf8ac078e)と[こちら](https://qiita.com/Hyperbolic_____/items/d2d5a25293ea64b7f7ed)の記事を読んでいただければ。SceneKitでの開発になりますので,UnityとSceneKitどちらを採用しようか迷っている方の参考にももしかしたらなるかもしれません。私は最初はUnityで製作していましたが,3Dプログラミングの使い勝手やコマンドの多さはUnityの方が優れていると思ったものの,プラグインが煩わしくなりSceneKitに変更しました。SceneKitはネット情報に溢れる情報こそ少ないですが,慣れてしまえば思い通りに操作できるかと思います。

##アプリの紹介
機能としてはものすごく単純で,前後左右にサイコロを動かす(回す)と動かす前にサイコロがあった底面のマス目が記録されます。
<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/daa9b171-dc58-0046-29b1-1ad8d54b0226.png"> <img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/816ea4e6-19e9-7897-28f9-2c99f20430b1.png">　<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/bc41afa9-6999-c2e6-4667-4002c3e4e696.png">

##苦労したところ
超単純な機能ですが,実現するためのアイディアを思いついたり,それらを実現するのはとても苦労しました。特にSceneKitは文献どころか日本語のサイトがほとんどありません。以下ぶつかった問題を挙げていきます。(みなさんであればどのように解決しますか？)

###回転軸と中心を任意に設定するメソッドがない
動画のような動きはUnityであれば専用のメソッドが用意されているのですが,SceneKitにはありません。(自身の軸(x,y,z)を中心として回転するメソッドはもちろんあります。)
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">UnityでいうところのRotateAroundをSceneKitでも再現できた <a href="https://t.co/6itcmFTaiE">pic.twitter.com/6itcmFTaiE</a></p>&mdash; 二次曲線 (@Hyperbolic____) <a href="https://twitter.com/Hyperbolic____/status/1295502162030518272?ref_src=twsrc%5Etfw">August 17, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

###配置したサイコロにとっての上下左右と,ユーザーが画面上で操作する上下左右が異なる

まず,3Dプログラミングには主に空間の絶対的な座標となるワールド座標と,配置されたオブジェクトがそれぞれ持つローカル座標,ローカル座標のうち,オブジェクトがカメラである時のカメラ座標,そしてデバイス上のスクリーン座標があります。[参考](https://yttm-work.jp/gmpg/gmpg_0001.html)ARKitではワールド座標がAR画面が起動した時のカメラ座標となります。

<Img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/4d356da8-6bab-548b-42e7-12bf327d3d5f.jpeg"> (右下の赤がワールド座標)
このアプリではスクリーンを画面をタップした時にその場所から平面が検出されれば画面上にサイコロが配置されます。この時,サイコロの座標の向き(x軸x,y軸,z軸)はワールド座標と同じになります。しかし,カメラ座標は起動後動かさないわけにはいかないので(平面を検出するためにデバイスを動かさなければならないため)ワールド座標のx,y,z軸からずれています。そのため,サイコロを右に動かそうと思い,右ボタンを押しても前に動いている,などといったことが起こってしまいます。


###「右」と「前->右->後ろ」では底面が異なる
これは当たり前といえば当たり前なのですが,同じ場所に辿りつくための行き方は無限にあり,ボタンを押した順序で一意に定まります。ボタンを押した回数で定まるわけではないです。そのため,ボタンを押すごとにサイコロの底面を取得しなければなりません。


### 2,3,6には底面の向きがあるため底面以外の面も取得しなければならない
この問題は上の問題の解決方法が思いつき,実装した後に発生した問題です。そのためこの問題を説明するには上の問題の解決方法について触れなければならないので,ここでは割愛します。
##解決するためのアイディアや方法

###「回転軸と中心を任意に設定するメソッドがない」の解決方法
以下のやり方で任意の回転軸と中心でオブジェクトを回転させる(サイコロが転がるアニメーションを作成する)ことができました。
その時のノートはこんな感じ。
<Img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/2bca4eb2-7945-b799-458f-4910c6bddf14.jpeg"><Img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/c7bd36d0-35c6-8322-0e3a-c2842f00b703.jpeg"><Img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/7a0e622b-05d9-5c48-ef37-c03ff4ae4911.jpeg">

####1.boxを追加する
右上の➕からboxを選択し、ドラッグ&ドロップします。その後、position(座標)を(0,0.5,0)、size(大きさ)を(1,1,1),Eular(回転)を(0,0,0)に設定します。
<img width="400" alt="スクリーンショット 2020-08-31 16.14.23.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/e749304b-5ebc-d092-92d1-daac16071623.jpeg">
####2.形を持たないnodeを追加する
右下の➕ボタンを押し、nodeを追加します。`<untitled>` が追加されればOKです。(以下これをemptyNodeとします。)
右側に回転する動きを再現したいので、中心となる立方体の辺の中点にemptyNode(0.5,0,0.5)に移動します。
<img width="400" alt="スクリーンショット 2020-08-31 16.15.06.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/17ae6c49-cac7-4949-7413-e4e42f0cd849.jpeg">


####3.emptyNodeの階層下にboxを配置する
階層関係にあるnodeにおいて、子nodeは親nodeの動きの影響を受けます。boxをemptyNodeの下にドラッグ&ドロップし、子nodeとして設定します。
<img width="300" alt="スクリーンショット 2020-08-31 16.15.44.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/e1b8e302-f1f7-2b61-222b-a3b4c8c92951.jpeg">
###4.emptyNodeを回転させる
emotyNodeのz軸を-90度回転させることで、回転を表現することができます。
<img width="300" alt="スクリーンショット 2020-08-31 16.16.04.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/b91bd478-d654-53d5-c9d8-27656cb5442e.jpeg"> <img width="300" alt="スクリーンショット 2020-08-31 16.16.22.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/ee2bebf8-48c3-b30a-0240-8efdf08010cf.jpeg">

###「配置したサイコロにとっての上下左右と,ユーザーが画面上で操作する上下左右が異なる」の解決方法

この問題はオブジェクトをのy軸をカメラ座標と同じにすることで解決しました。
<Img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/9bb20d6f-3a1e-7866-cc1f-2e3acbba76fe.jpeg">

###「右」と「前->右->後ろ」では底面が異なる」の解決方法

この問題はサイコロを立方体として考えるのではなく,正方形が6枚組み合わさっているものとして捉えることで解決しました。もともと立方体(SCNBox)で回転を考えていたのですが,底面を判定できないことに気づき,正方形(SCNPlane)を組み合わせ,y座標(ワールド座標)が最も小さいものを記録すれば良いということに気づきました。ボタンを押すたびにy座標が最も小さいSCNPlaneを判定するので,それまでに押されたボタンに依存することなく記録することができます。
<Img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/eff4c8ee-9c12-6a09-207e-4f6fdfa956fe.jpeg">


###「2,3,6には底面の向きがあるため底面以外の面も取得しなければならない」の解決方法
この問題は上述し立方体を正方形が6枚組み合わせたものとして考えることに気づいた後にぶつかった問題です。底面の向きを決めるためには側面にある4つ面のうちどれかを取得しなければなりません。つまり,正方形のx座標をワールド座標で置き換えた時に最も大きくなるものを取得しなければなりません。さらに,サイコロのy座標は**配置したサイコロにとっての上下左右と,ユーザーが画面上で操作する上下左右が異なる」**問題を解決するために回転させているので,x座標はワールド座標への正射影を考える必要があります。
その時のノートはこんな感じ。
<Img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/82704b33-f2b2-0be4-b6bd-36999fecd32d.jpeg"><Img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/90e341ef-9829-1c0a-3860-7a8d3bec0355.jpeg"><Img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/eb77e416-5bfe-9122-a9ea-dc92b6cb1f5f.jpeg"><Img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/a6a1c7f0-0b2e-ec20-2b55-405b6e78177e.jpeg"> <Img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549093/17c58e02-7aef-38bb-1f7a-c2996d39694d.jpeg">

##技術を得るために
###数少ないネットの情報を漁りまくる
SceneKitに関する情報はめちゃくちゃ少ないです。SceneKitの基本的なことは[このサイト](https://github.com/ToshihiroGoto/techbookfest3_SceneKit)で,３dプログラミングについてはUnityで学びました。ARKitについてはBOOTHで販売されている[こちら](https://booth.pm/ja/items/1036430)のテキストがすごく丁寧で分かり易かったです。
###Qiitaでのアウトプット
SceneKitに関する情報を増やしたい！といった思いと備忘録がわりにQiitaにアウトプットしました。

- [https://qiita.com/Hyperbolic_____/items/97dfc180299f03057178](https://qiita.com/Hyperbolic_____/items/97dfc180299f03057178)
- [タップされるたびにnodeを置き換える方法](https://qiita.com/Hyperbolic_____/items/223804f0263fb56c1b44)
- [viewの中心の座標を取得する](https://qiita.com/Hyperbolic_____/items/e2d52e356bdedb1b7a17)
- [ARオブジェクトを常にカメラの方向を向いた状態で配置する](https://qiita.com/Hyperbolic_____/items/77f2aa95f1c0e6e6ddbd)
- [SceneKitでサイコロが転がるようなアニメーションを再現する](https://qiita.com/Hyperbolic_____/items/16adeeb3c2a2e631683c)
- [StackViewでどのデバイスでもレイアウトが崩れない十字キーを作成する
](https://qiita.com/Hyperbolic_____/items/eab921f50ea8b1429a32)
- [SCNPlaneに画像を貼り付ける](https://qiita.com/Hyperbolic_____/items/9dd322db82fddcf932b4)
- [Int型を拡張して3の倍数か判定するメソッドを作成する](https://qiita.com/Hyperbolic_____/items/d04a75bdd7c0e13dcf4a)

##最後に
SceneKitというわりとマイナーなフレームワークでの開発だったため,とにかくインプットよりもアウトプット中心の学習だったように思います。その中で三角比や場合わけ(最終的には256通りになった)など高校数学で学習したことが活かされる場面多々あり,嬉しく思いました。3Dプログラミングはとても難しいですが,その分思い通りに動いた時の達成感は筆舌に尽くしがたいものです。このアプリを通して得られた知見がこの先のアプリの開発で生かされたり,アウトプットした記事が誰かの役に立てば幸いです。