tag:blogger.com,1999:blog-2119500771449205472024-03-05T15:50:01.111+09:00Hello AR !!ARKitの解説と個人開発の紹介ブログすいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.comBlogger51125tag:blogger.com,1999:blog-211950077144920547.post-22690369900733196902020-01-26T22:57:00.000+09:002020-01-26T22:57:41.647+09:00事故マップをアップデートしました<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6TeBA8ERQZvhjRhPycZPBRBNZtHUBQlVLmllupk9t87X2-6RLtBjbDzGo_CY9c5uY8eJ3IsZEvFJYAicr17pCX1cSxP9MFAzaO-imd3GJVkM2OLw3zKa4zjMmfu0ihhYcQt5-Mukq9hg/s1600/45ffbf2eaf269707c6585b2bc684f437_s.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="480" data-original-width="640" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6TeBA8ERQZvhjRhPycZPBRBNZtHUBQlVLmllupk9t87X2-6RLtBjbDzGo_CY9c5uY8eJ3IsZEvFJYAicr17pCX1cSxP9MFAzaO-imd3GJVkM2OLw3zKa4zjMmfu0ihhYcQt5-Mukq9hg/s320/45ffbf2eaf269707c6585b2bc684f437_s.jpg" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<h2>
事故マップをアップデートしました</h2>
<div>
<br /></div>
<div>
初回版のリリースから一ヶ月以上経ってしまいましたが、<a href="https://jikomap.site/">事故マップ</a>をアップデートしました。</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSSxL8XvX4y9JPUuRqXGl27mtGdt6s6Y09KO8mfcEnsw27MaNy4BoolMuL4rqNOQTI4LJL0zjBlMSCDVESmcBHHTag7YPg87x2u93jBZ9kX-Ut8Vw4pAPN6gTiVB0s8C8paV6AN-D71Hc/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2020-01-26+22.50.14.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="324" data-original-width="1149" height="179" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSSxL8XvX4y9JPUuRqXGl27mtGdt6s6Y09KO8mfcEnsw27MaNy4BoolMuL4rqNOQTI4LJL0zjBlMSCDVESmcBHHTag7YPg87x2u93jBZ9kX-Ut8Vw4pAPN6gTiVB0s8C8paV6AN-D71Hc/s640/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2020-01-26+22.50.14.png" width="640" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
画面の右上に最近追加された情報をNotificationとして表示されるようにしました。</div>
<div>
この機能を実装するにあたり、コンポーネント間のデータ共有方法を見直す必要がありVuexを使って全面的にアプリをリファクタリングしました。</div>
<div>
<br /></div>
<div>
MVVMが何たるかを理解していない状態からだったため、時間はかかってしまいましたが非常に勉強になりました。得た知見は近いうちにQiitaに投稿したいと思います。</div>
すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-33317983168177702742019-12-10T00:11:00.000+09:002019-12-10T00:11:30.336+09:00何故「事故マップ」というWebサービスを作ったか<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQZml224bc-4B86jbqdqtqKLw-5wrF9I2_L2EFStz9F0z414OZ2VG5XUVhMwgYzlTXEIkdQlfDfwS3_2-DPgS4SwYoxySvfnitWSqEzHEoLXx22gf5iaslTFZdoojI4Dh6SURqTBdBoJU/s1600/exp2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="820" data-original-width="1600" height="328" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQZml224bc-4B86jbqdqtqKLw-5wrF9I2_L2EFStz9F0z414OZ2VG5XUVhMwgYzlTXEIkdQlfDfwS3_2-DPgS4SwYoxySvfnitWSqEzHEoLXx22gf5iaslTFZdoojI4Dh6SURqTBdBoJU/s640/exp2.png" width="640" /></a></div>
<br />
<br />
<h2>
何故「事故マップ」というWebサービスを作ったか</h2>
<br />
先日、<a href="https://jikomap.site/">事故マップ</a>というサービスをリリースしました。<br />
<br />
基本的に仕事でも個人でもモバイルアプリをメインにしている私が何故Webサービスに手を出したのかの理由を紹介します。<br />
<br />
その理由はシンプルにモバイルアプリだけでは継続的なユーザーの利用は見込めないと思ったからです。<br />
<br />
個人開発のくくりで考えたときにスマートフォンの出始めは電卓アプリなどの単純なアプリだけで何十万も稼ぐことができました。その後トレンドはカジュアルゲームに移行し、アイディアと工夫で大金を稼ぐ事ができました。<br />
しかし、時代は進みスマートフォンがより一般化する事で、ユーザーは物珍しさでアプリを利用することから本当に役に立つものを選んで利用するようになったと思います。<br />
<br />
その中で、ユーザーに利用してもらえるアプリは2パターンに分類できると思います。<br />
<br />
<br />
<ul>
<li>特定の役に立つアプリ 例)Twitter GoogleMap 写真加工アプリ </li>
</ul>
<div>
<ul>
<li>今までにできなかった事ができるようになるアプリ 例)fontgenic ARアプリ</li>
</ul>
<div>
<br /></div>
</div>
<div>
ARアプリとしてAR CakeDivider や AR Mini Sketchを作ってみましたが、あまりインパクトのあるものではなかったと評価しています。</div>
<div>
<br /></div>
<div>
UnityのAR Foundationを使って今後もARアプリの開発は続けて行きたいと考えていますが、将来の自分の幅を広げるためにも前者の領域にトライしてみたいと思うようになりました。</div>
<div>
<br /></div>
<div>
そして、役に立つアプリは自分の知らない情報を取得できる事が根底にあると考えました。しかし、Newsサイトなどを運営するほど自分には時間が無いためユーザーの情報を交換できるSNS的なサービスを作ろうと考えました。</div>
<div>
<br /></div>
<div>
奇しくも丁度1ヶ月半前に交通事故を起こしてしまい、相手方との自己負担割合を決める上で事故当時の情報(映像)がとても重要になるということを実感しました。時と場合によりますがその情報は当事者にとってはとても価値のあるものになるため、そのような情報を共有できるサービスがあれば皆の役に立つのでは無いかと思い「事故マップ」というサービスを作りました。</div>
<div>
<br /></div>
<div>
この記事を書いた一年後には皆様に使っていただけているサービスになっていることを願いながら、エンハンスや他の開発を頑張って行こうと思う今日この頃です。</div>
<div>
<br /></div>
<div>
<br /></div>
<br />
<br />
<br />
<br />すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-32482322794692844392019-09-01T16:09:00.000+09:002019-09-01T16:09:25.101+09:00Blenderを使って3Dデータを作成&表示させてみよう<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3UPH_k7U4eqTuUmqLFPi4NPfWo3EcmU5P2hkvtX-CTE0MbCnAI2sFmJ85jFoi9crf1WlQRFP9H5rOtA9WDrb_KrXBi_KU52y28z2gadudyrtz_F1JsikSlvotU1GO5EW-LeClu6u2zVk/s1600/e3b07d4167ab46ffc6cbd0c3bbb5b80a_s.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="492" data-original-width="640" height="307" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3UPH_k7U4eqTuUmqLFPi4NPfWo3EcmU5P2hkvtX-CTE0MbCnAI2sFmJ85jFoi9crf1WlQRFP9H5rOtA9WDrb_KrXBi_KU52y28z2gadudyrtz_F1JsikSlvotU1GO5EW-LeClu6u2zVk/s400/e3b07d4167ab46ffc6cbd0c3bbb5b80a_s.jpg" width="400" /></a></div>
<br />
<h2>
Blenderを使って3Dデータを作成&表示させてみよう</h2>
<br />
これまではサンプルをベースに紹介をしてきましたが、実際にアプリを作成する際には独自の3Dデータを表示することになると思います。<br />
<br />
3DモデリングはShadeやAutoCADなどの有償ソフトで行うことが多いですが、個人アプリ開発の場合では無償のBlenderでも十分こと足りると思います。<br />
<br />
今回はBlenderを使って3Dデータを作成する方法と、ARKitで表示する方法を紹介します。<br />
<br />
<br />
<h2>
Blenderのインストール</h2>
<br />
この記事作成時のBlenderの最新バージョンは2.8.0になります。<br />
<br />
<a href="https://www.blender.org/">こちら</a>からBlenderをダウンロードしインストールを行います。<br />
<br />
2.7.9では日本語化するには別途作業が必要でしたが、2.8.0ではデフォルト日本語に対応しています。<br />
<br />
<br />
<h2>
3Dデータの作成</h2>
<br />
Blenderを使って木の箱とボールを作ります。<br />
<br />
Blenderを起動すると下記のようなデフォルトプロジェクトが表示されます。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9-EycjJIPIpdbP_poMN7YS6PlqwjYV8WvhbYYH-iczP0tmmVgeXpebd4hirkA4Oi6eGJ58Ki_QwObiciPPLXEciii-0imGKOlMnmpZx1MhQMItV0PApun5FZj97OMpjaajInYgETv170/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+9.52.15.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1039" data-original-width="1600" height="258" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9-EycjJIPIpdbP_poMN7YS6PlqwjYV8WvhbYYH-iczP0tmmVgeXpebd4hirkA4Oi6eGJ58Ki_QwObiciPPLXEciii-0imGKOlMnmpZx1MhQMItV0PApun5FZj97OMpjaajInYgETv170/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+9.52.15.png" width="400" /></a></div>
<br />
<br />
まずは、カメラとライトは不要なので右のアウトライナーから右クリックで選択し「削除」を実行します。<br />
<br />
木の箱はデフォルトのCubeを使うため、ボールを追加します。<br />
<br />
オブジェクト画面のメニューより追加 > メッシュ > UV球を選択します。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNIaYeqMF1KLLDDT8nVa6KsnBrFltcMSegBtcgF2tmdy7g5sp6ELroQAzrHyNJvZOI3nCqOTztA2u0YlR2AMfHk4jdUE6iNdm0fAhj8Kvk5FU6sX-76NSmGKwOIqvbUKmJAUr3JDShJ2E/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+9.55.25.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1039" data-original-width="1600" height="258" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNIaYeqMF1KLLDDT8nVa6KsnBrFltcMSegBtcgF2tmdy7g5sp6ELroQAzrHyNJvZOI3nCqOTztA2u0YlR2AMfHk4jdUE6iNdm0fAhj8Kvk5FU6sX-76NSmGKwOIqvbUKmJAUr3JDShJ2E/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+9.55.25.png" width="400" /></a></div>
<br />
<br />
<br />
こちらのカーソルの位置に球が作成されるので事前に配置したい位置にカーソルを移動しておきます。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPo8WPBvvwE4vzijrNizp08vGfjIdyNW8LXJn18szSphMMug5TIReT5wwowac49tqcv-AnIpFBJmIg_VE9Wrfw_sothHD9SqSXgA2nq0Tp1di3yN_majbOmxFZOHEagDTKUBVw7Kx8nBI/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+9.56.18.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="51" data-original-width="60" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPo8WPBvvwE4vzijrNizp08vGfjIdyNW8LXJn18szSphMMug5TIReT5wwowac49tqcv-AnIpFBJmIg_VE9Wrfw_sothHD9SqSXgA2nq0Tp1di3yN_majbOmxFZOHEagDTKUBVw7Kx8nBI/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+9.56.18.png" /></a></div>
<br />
<br />
デフォルでは「球」となっていますが、ARKit内で操作し易いように右のアウトライナーより名前を「Ball」に変更しておきましょう。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh17xRWhOXzA4leeU_UJjXcda8zPewaoawge7jUc_KF80GZbBpXKgodvun11X3TgcpVvkmFQ9G0JQWErcqazIfg3FXaUj8GtpYyC4nQGrvMgWdIBR9FG_PPLo425VEWiFZmY-mhCKNjWnM/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+9.58.07.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="897" data-original-width="1447" height="247" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh17xRWhOXzA4leeU_UJjXcda8zPewaoawge7jUc_KF80GZbBpXKgodvun11X3TgcpVvkmFQ9G0JQWErcqazIfg3FXaUj8GtpYyC4nQGrvMgWdIBR9FG_PPLo425VEWiFZmY-mhCKNjWnM/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+9.58.07.png" width="400" /></a></div>
<br />
ひとまず、シーンの中に箱とボールを作ることができました。<br />
<br />
<br />
<h2>
3Dデータのエクスポートとプロジェクトへの追加</h2>
<br />
Blenderメニューのファイル > エクスポート > Collada (.dae) を選択してデータをエクスポートします。<br />
<br />
ここでは3d_data.daeと名前を付けてエクスポートを行います。<br />
<br />
作成したdaeファイルはXcodeのプロジェクトにドラッグ&ドロップで追加を行います。<br />
<br />
もし、art.scnassetsのアセットカタログがプロジェクトにあるのであればそこに追加を行いましょう。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6L7TES01dEn0Y3ACRUXI3ZwUkZV1YfCju-uwJb64iagJCrxekdgopUgrp97hBgq-01EmZzawH9RC6nZ9Ku7WFoGh3PCqoSFUzAo-5O21D95POpxJobuJ66ezyK9Q8JAZNmI2B11cd9RQ/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+15.02.34.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="185" data-original-width="270" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6L7TES01dEn0Y3ACRUXI3ZwUkZV1YfCju-uwJb64iagJCrxekdgopUgrp97hBgq-01EmZzawH9RC6nZ9Ku7WFoGh3PCqoSFUzAo-5O21D95POpxJobuJ66ezyK9Q8JAZNmI2B11cd9RQ/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+15.02.34.png" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
ない場合はプロジェクト直下に置いても構いませんが、プロジェクトの Copy Bundle Resourcesにdaeファイルが追加されていることを確認しましょう。<br />
※art.scnassetsがCopy Bundle Resourcesに追加されている場合は不要です<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgY_50kaSC0xLMv-tdIaogMFNpLbPWN0Pjbw1irGzX66myRKyhtdGmG9G52oEkulPeVpZda_91xJmBdoHLYlovl6NjSCFzggxTBakqxL_FmuHu5ZvA72zEU63d7VvccLh-28oBGcg-lxzE/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+15.03.17.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="374" data-original-width="1079" height="219" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgY_50kaSC0xLMv-tdIaogMFNpLbPWN0Pjbw1irGzX66myRKyhtdGmG9G52oEkulPeVpZda_91xJmBdoHLYlovl6NjSCFzggxTBakqxL_FmuHu5ZvA72zEU63d7VvccLh-28oBGcg-lxzE/s640/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+15.03.17.png" width="640" /></a></div>
<br />
<br />
<h2>
scnファイルへの変換と位置の調整</h2>
<br />
このままdaeファイルの状態でも3Dデータを読み込むことはできるのですが、データ表示位置の調整やテクスチャの設定はscnファイルの方がやり易いのでdaeファイルをscnファイルへと変換を行います。<br />
<br />
Xcodeの左のペインより、3d_data.daeを選択し、メニューの Editor > Convert to SceneKit scenn file formatを実行します。<br />
<br />
これでファイルがscnファイルへと変換され下記のように表示がされます。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCgipJCSIcS21NGrZwwGaIwQIs8rcgYcEDUw17RR5RCzfV6f_Wqk2bSsGGJcPszOd8CbMpaU2lrbsxWr7job1CB3ZWXXtita0J5iO8lxrIdM8hyys66Dk0G14mM5UchRowHI-7lzQokkk/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+15.13.32.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="339" data-original-width="939" height="142" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCgipJCSIcS21NGrZwwGaIwQIs8rcgYcEDUw17RR5RCzfV6f_Wqk2bSsGGJcPszOd8CbMpaU2lrbsxWr7job1CB3ZWXXtita0J5iO8lxrIdM8hyys66Dk0G14mM5UchRowHI-7lzQokkk/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+15.13.32.png" width="400" /></a></div>
<br />
<br />
3Dカーソルがこのデータの原点になります。そのため、このままAKitでテープル上にデータを表示すると、テーブルにボールの半分がめり込んでしまいます。<br />
<br />
そのため、面倒でも一度SCNNodeを親に設定し、表示位置を調整する必要があります。<br />
<br />
左のScene graphより「+」を選択し、ノードを追加します。<br />
<br />
この時の名前は対応付けがわかるようにCubuNodeとしておきます。<br />
<br />
CubeNodeを作成したらScene graph上でCubeでCubuNode配下に移動させます。<br />
<br />
そして、CubeNodeがを選択した時に3Dカーソルがデータの底に来るようにCubeの位置を変更します。<br />
<br />
3Dカーソルの緑の矢印(Y軸)を選択して移動させます。<br />
<br />
このとき視点が真横に来ないと調整が難しいですが、画面の左下にあるカメラマークでFrontを選択しておくと視点が真横に来ます。<br />
<br />
調整が済むとこのような状態になります。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgD4o6KJlH6uGr3d8JML8AvZP5hDhZVUJaKXTxkH7Zin5n6s7voCqSM5L85a4dTN4hEBTGsXvCYCDU04FMPiVQyW_b9A3I4v49gZl4kuqjfCKkcDFEoSHo3oDxfHkYer-GtShcJF-Udi2U/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+15.42.39.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="569" data-original-width="838" height="271" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgD4o6KJlH6uGr3d8JML8AvZP5hDhZVUJaKXTxkH7Zin5n6s7voCqSM5L85a4dTN4hEBTGsXvCYCDU04FMPiVQyW_b9A3I4v49gZl4kuqjfCKkcDFEoSHo3oDxfHkYer-GtShcJF-Udi2U/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+15.42.39.png" width="400" /></a></div>
<br />
<br />
<h2>
テクスチャ、色の設定</h2>
<br />
このままだと真っ白なデータが表示されてしまうためテクスチャの設定と色の設定を行います。<br />
<br />
<br />
<ul>
<li>箱 → 木目のテクスチャを設定</li>
<li>ボール → テクスチャを使わず、紫色の設定</li>
</ul>
<br />
<br />
まずは箱の設定にテクスチャを設定します。<br />
<br />
daeファイルを読み込んだときと同様にテクスチャ画像をXcodeプロジェクトにドラッグ&ドロップします。<br />
<br />
次に右のペインでマテリアルのタブを選択します。<br />
<br />
このときMaterialsに何も表示されていなかったら「+」ボタンを押して新しくMaterialを追加してください。<br />
<br />
この状態ではPreviewは味気のない球が表示されているはずです。<br />
<br />
Properties > Diffuseを選択し先程追加した画像を選択します。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOPtUVBvtk5or81Dr-fOC5GFgPR5WOLgFTJJN2YtsP3nesSjQgB5GzxChyc1l0vL_RZnRoTfO3-u3-3b2QVVSGFFwy27EpG0iFVSzvFuXlWIGWogvUzxFscZ9Z35ZDPr2aDyBCXG6Se6c/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+15.50.06.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="566" data-original-width="862" height="262" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOPtUVBvtk5or81Dr-fOC5GFgPR5WOLgFTJJN2YtsP3nesSjQgB5GzxChyc1l0vL_RZnRoTfO3-u3-3b2QVVSGFFwy27EpG0iFVSzvFuXlWIGWogvUzxFscZ9Z35ZDPr2aDyBCXG6Se6c/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+15.50.06.png" width="400" /></a></div>
<br />
<br />
このとき画像と一緒に色も選択肢として表示されましたが、画像でなく色を選択すれば3Dデータに色をつけることができます。<br />
<br />
ボールも箱と同様にDiffuseを設定します。<br />
<br />
うまく設定ができれば最終的にはこのようになります。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5tFhbynJ2rH8_sDwfMz4sUv23BeyiKK7QHA1ul_y2HBKJXZNvHebc80JYh4RUY6zbtd0Rj1LNly4RB7uJtQWIWM_fUQx-oS7beX7hwS0MFpfKAoCwm4WK5noRPdstd5pFvuSQuQMQRfE/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+15.57.17.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="568" data-original-width="1184" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5tFhbynJ2rH8_sDwfMz4sUv23BeyiKK7QHA1ul_y2HBKJXZNvHebc80JYh4RUY6zbtd0Rj1LNly4RB7uJtQWIWM_fUQx-oS7beX7hwS0MFpfKAoCwm4WK5noRPdstd5pFvuSQuQMQRfE/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-09-01+15.57.17.png" width="400" /></a></div>
<br />
<br />
<h2>
プログラムへの読み込み</h2>
<br />
ここまで来たら最後はプログラム上で作成した3Dデータを読み込むだけです。<br />
<br />
サンプルの飛行機を読み込んだときと同じようにファイルを指定して読み込むだけです。<br />
<br />
具体的には下記のように書きます。<br />
<br />
<div style="background-color: #1f1f24; color: white; font-family: Menlo; font-size: 12px; font-stretch: normal; line-height: normal;">
<span style="color: #fc5fa3;"><b>guard</b></span> <span style="color: #fc5fa3;"><b>let</b></span> dataScene = <span style="color: #7ac8b6;">SCNScene</span>(named: <span style="color: #fc6a5d;">"art.scnassets/3d_data.scn"</span>) <span style="color: #fc5fa3;"><b>else</b></span> { <span style="color: #fc5fa3;"><b>return</b></span> }</div>
<div style="background-color: #1f1f24; color: white; font-family: Menlo; font-size: 12px; font-stretch: normal; line-height: normal;">
<span style="color: #fc5fa3;"><b>if</b></span> <span style="color: #fc5fa3;"><b>let</b></span> node = dataScene.<span style="color: #7ac8b6;">rootNode</span>.<span style="color: #99e8d5;">childNode</span>(withName: <span style="color: #fc6a5d;">"cubeNode"</span>, recursively: <span style="color: #fc5fa3;"><b>true</b></span>) {</div>
<div style="background-color: #1f1f24; color: white; font-family: Menlo; font-size: 12px; font-stretch: normal; line-height: normal;">
<span style="color: #91d462;">sceneView</span>.<span style="color: #7ac8b6;">scene</span>.<span style="color: #7ac8b6;">rootNode</span>.<span style="color: #99e8d5;">addChildNode</span>(node)</div>
<div style="background-color: #1f1f24; color: white; font-family: Menlo; font-size: 12px; font-stretch: normal; line-height: normal;">
</div>
<div style="background-color: #1f1f24; color: white; font-family: Menlo; font-size: 12px; font-stretch: normal; line-height: normal;">
}</div>
<br />
3d_data.scnでは二つの3Dデータを含んでいます。<br />
<br />
読み出したいデータに合わせてchildeNode()で検索する名前を変更します。<br />
<br />
なお、ここで気をつけて欲しいのは、検索する名前は3Dデータ自体ではなくその親ノードを指定することです。<br />
<br />
直接3Dデータを読み込むことができますが、原点が底に来ていないため想定した位置とずれてしまうことになるので注意してください。<br />
<br />すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-16304377318369689002019-08-22T23:13:00.002+09:002020-02-22T22:54:00.429+09:00Sad Face Makerをリリースしました最近モバイルアプリの個人開発が停止中だったのですが、WWDC2019の刺激を受けて久しぶりに新しいアプリを作りました。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqaDMLZ19p8QfkN0Y7XVrJYR8jC2VVdUszjwXaJ7S5AFGdOkc6kXON5W1R_g1YE7jst5lsCrqXFZWhTQi7cvas6W5z4rpAcqsrLn9at5UotziCUKdDr7N5nWkc7oJuNLaNlCXVKNFOnas/s1600/icon_83.5%25402x.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="167" data-original-width="167" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqaDMLZ19p8QfkN0Y7XVrJYR8jC2VVdUszjwXaJ7S5AFGdOkc6kXON5W1R_g1YE7jst5lsCrqXFZWhTQi7cvas6W5z4rpAcqsrLn9at5UotziCUKdDr7N5nWkc7oJuNLaNlCXVKNFOnas/s1600/icon_83.5%25402x.png" /></a></div>
<br />
<br />
<a href="https://apps.apple.com/jp/app/sad-face-maker/id1477206661">Sad Face Maker</a>という写真の編集アプリです。Twitterの投稿機能について学ぶことができました。こちらは<a href="https://qiita.com/noby111/items/532f3d806b19ab46433a">Qiitaの記事</a>としてまとめています。<br />
<br />
アプリを使うとこのようなGIFアニメーションを簡単に作ることができます。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-EB70-Cee0htNDS5ZX2dMCuwt9Xyg6Tg0CykITCNBZhR-9cK89V1hnnxO4tCE1hfEAOhxOZalVe_URn8JU06M4Pvz9ihsmd6JXBANoL5q7g4C6Wnm921hqcb2vFIceC_VGr9f4Q7c0Jg/s1600/IMG_2801.GIF" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="617" data-original-width="375" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-EB70-Cee0htNDS5ZX2dMCuwt9Xyg6Tg0CykITCNBZhR-9cK89V1hnnxO4tCE1hfEAOhxOZalVe_URn8JU06M4Pvz9ihsmd6JXBANoL5q7g4C6Wnm921hqcb2vFIceC_VGr9f4Q7c0Jg/s320/IMG_2801.GIF" width="194" /></a></div>
<br />
<br />すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-20931747842506085542019-04-19T23:23:00.001+09:002019-04-19T23:29:27.832+09:00AR Mini Sketchをリリースしました<div style="text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHEAgSe9jAfQEhiO90l8SwQ6nuP7FzazLtQmoPSiTocYb1SJqovuG70xJmUZ9Xbx-cirxcWAsekwGKUewnfewx56M6w2qx6NliVm776BpYLaInLV6VQIRtnjUi3gwhMARJz5mZXKW_NoM/s1600/icon.png" imageanchor="1"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHEAgSe9jAfQEhiO90l8SwQ6nuP7FzazLtQmoPSiTocYb1SJqovuG70xJmUZ9Xbx-cirxcWAsekwGKUewnfewx56M6w2qx6NliVm776BpYLaInLV6VQIRtnjUi3gwhMARJz5mZXKW_NoM/s320/icon.png" width="320" /></a></div>
<br />
<a href="https://itunes.apple.com/jp/app/ar-mini-sketch/id1455009455">ARMiniSketch</a>というアプリをリリースしました。<br />
<br />
画像の切り抜きやUIGestureのカスタマイズ方法などを学ぶことができました。<br />
もしダウンロード数が増えて来たらAdMobの成果なども公開していきたいと思っています。<br />
<br />
AppStoreには載せられなかったのですが、使い方を紹介したビデオです。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dwUzlh7a6Rbbm6bEk4ya1oQ-u8b1TSUcvCKP3nDxcEqc6rPZ3HkdVlNsL7U2_AMQqYZ0fBHsXXYSiiKBO3yCQ' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div>
<br />すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-70572833700371678132019-03-21T22:16:00.000+09:002019-03-21T22:19:58.800+09:00ARKitのサンプルに影を追加しよう<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwMJgTpD7tyRtnAGXplpJXAnIWPOptcN0BpC8g_UlukO_UF_dutD25XxyYlRPN9NznEnZ57K-ow6F2O5QNULXb02QsT2BRg45Fr7HBAPU_yIprgN0CGshW6cym4oAM57htTjn_JbWdVU8/s1600/b4dcbb6e10da433d693f50f132d286ac_s.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="424" data-original-width="640" height="424" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwMJgTpD7tyRtnAGXplpJXAnIWPOptcN0BpC8g_UlukO_UF_dutD25XxyYlRPN9NznEnZ57K-ow6F2O5QNULXb02QsT2BRg45Fr7HBAPU_yIprgN0CGshW6cym4oAM57htTjn_JbWdVU8/s640/b4dcbb6e10da433d693f50f132d286ac_s.jpg" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h2>
ARKitのサンプルに影を追加しよう</h2>
<div>
<br /></div>
<div>
ここまでサンプルアプリをよりリアルにする方法として、<a href="https://www.nsunrise.work/2019/03/arkit_5.html">ライトを追加する方法</a>を紹介しましたが、何か物足りないと感じてはいなかったでしょうか?</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
現実世界では物体に光を当てると影ができますが、サンプルアプリの飛行機には影がありませんでした。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
影を付けることでさらにリアルさを演出することができますので、今回はその方法をご紹介します。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h2>
影を付ける方法</h2>
<div>
<br /></div>
<div>
影を付ける方法は次の二つがあります。</div>
<div>
<br /></div>
<div>
<h3>
影の画像ファイルを用意して物体の下に表示する</h3>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjT7fcp0MKfyylDbJs3ccy1oHJ31-MafFzzJPAXpKNa8TZnON-5542TRFLYlxNeikaw-zIjvHmQ1pz6G_IAhwI_gX7VxCHlkZ7Pm3QkGsG5PRtC5X-jJp7BT2z6wCT5vZ5VOd1S7Tk3gJU/s1600/chair.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="370" data-original-width="640" height="185" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjT7fcp0MKfyylDbJs3ccy1oHJ31-MafFzzJPAXpKNa8TZnON-5542TRFLYlxNeikaw-zIjvHmQ1pz6G_IAhwI_gX7VxCHlkZ7Pm3QkGsG5PRtC5X-jJp7BT2z6wCT5vZ5VOd1S7Tk3gJU/s320/chair.png" width="320" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<h3>
物体にCast Shadowを有効にした光を物体に当てる</h3>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfl3dFyAHQVig5Qo_G2vnDjulSH6tgsJ4MnSC5zbXY91-l94xroZ-RjAN_YVITOQeCR60ZTvcGZBDVyo46kewP7d_Di86VQ7IYO6Fip8fzUsszDU2aUss8tFf6-ruXgDkjo1hNhRv0lOo/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+15.36.58.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="380" data-original-width="554" height="219" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfl3dFyAHQVig5Qo_G2vnDjulSH6tgsJ4MnSC5zbXY91-l94xroZ-RjAN_YVITOQeCR60ZTvcGZBDVyo46kewP7d_Di86VQ7IYO6Fip8fzUsszDU2aUss8tFf6-ruXgDkjo1hNhRv0lOo/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+15.36.58.png" width="320" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
</div>
<div>
前者は方法としてはシンプルですが、それぞれの物体に合った影の画像を用意するのは面倒です。</div>
<div>
<br /></div>
<div>
さらに、対象の物体が移動した場合にはそれに合ったファイルを全て用意するのは現実的ではありません。</div>
<div>
<br /></div>
<div>
今回は後者の方法を紹介します。</div>
<div>
<br /></div>
<div>
<br /></div>
<h2>
ship.scnを修正する</h2>
<div>
<br /></div>
<h3>
1. ShipMeshの位置を修正する</h3>
<div>
<br /></div>
<div>
この後にライトと影を写す平面を追加するのですが、shipの子ノードとして追加します。</div>
<div>
<br /></div>
<div>
全ての子ノードがshipと同じ位置にあった方が修正がやり易いので、飛行機本体のshipMeshを親ノードのshipと同じ位置まで移動させます。</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijVD05KWWjl6KALX00bMN1AH0z7Xt6nE696Ta7OyLl9nBdXrpiIQQ-xDLIIYUbPVHT6wpnsgAe0-zuoAoIcHyC9Lu-Ei4LoI_74_qOvoyEuPeM2rdkMUUOhmjM6ZUekKIEi6wIeccIvPI/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+16.34.36.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="451" data-original-width="955" height="188" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijVD05KWWjl6KALX00bMN1AH0z7Xt6nE696Ta7OyLl9nBdXrpiIQQ-xDLIIYUbPVHT6wpnsgAe0-zuoAoIcHyC9Lu-Ei4LoI_74_qOvoyEuPeM2rdkMUUOhmjM6ZUekKIEi6wIeccIvPI/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+16.34.36.png" width="400" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
ship.scnを開き、左のペインからshipMeshを選択し、Transformsのz positionを0に変更してください。<br />
<br />
※このまま進めると、飛行機を回転させた時に翼が地面にめり込むのでy座標も0.2に設定してください</div>
<div>
<br />
<br /></div>
<h3>
2. 影を表示するための平面を追加する</h3>
<div>
<br /></div>
<div>
影を表示するための平面を追加します。</div>
<div>
<br /></div>
<div>
影は飛行機本体の下に表示するため、平面は必ずshipMeshと比べて低い位置に配置します。</div>
<div>
<br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkqAkJX25PdrY28zkt9G0zvEuzzNXm8n9DIO77d7-3DBrZTrm2ANlyZAdwlwesnOXEhM1kzIn1h4vxibeCbdCkH1XIJ1blKyk8ZYzUFV6gye_Tv7mViIpaUMzzTyQH47gaKzZw0-M1VGQ/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+16.43.03.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="364" data-original-width="709" height="205" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkqAkJX25PdrY28zkt9G0zvEuzzNXm8n9DIO77d7-3DBrZTrm2ANlyZAdwlwesnOXEhM1kzIn1h4vxibeCbdCkH1XIJ1blKyk8ZYzUFV6gye_Tv7mViIpaUMzzTyQH47gaKzZw0-M1VGQ/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+16.43.03.png" width="400" /></a></div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div>
右上のオブジェクトボタンを押し、Planeを選択し、飛行機の下あたりにドラッグ&ドロップします。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMYVfYZEYMG0mPRZOqCGEgipluPmujXvlNAyko5b8uEC402WxpSTpiPvVevbGXJ13Xt_LzRgNKzHcyF1b9u8M8dViN_KQgboqI2FpEK1XX-H9ePqkZ9lkKx59KmbonCLQEM7AE1eG88UM/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+16.58.33.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="775" data-original-width="1412" height="218" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMYVfYZEYMG0mPRZOqCGEgipluPmujXvlNAyko5b8uEC402WxpSTpiPvVevbGXJ13Xt_LzRgNKzHcyF1b9u8M8dViN_KQgboqI2FpEK1XX-H9ePqkZ9lkKx59KmbonCLQEM7AE1eG88UM/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+16.58.33.png" width="400" /></a></div>
<br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br /></div>
<div>
次に、planeを選択し、角度と位置を調整します。<br />
<br />
追加した状態では平面は縦向きです。横にするため、Euerのxを-90にします。<br />
<br /></div>
<div>
位置はPositionのx, y, zをそれぞれ、0, -0.3, 0と入力します。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEid9dAiAFhKjqPVRngDRO3LKMwBAJAYrtbMzbx9c64VM3Tk9SLaX-3sxapyQIQCUekRT2av7KIkV8rolsyzVCXHoCLP_ie6Nl6IuAwiYpiRw2SkSki1pTNqOsXaWpiK7A8LPcX2yFBBlws/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+16.48.03.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="451" data-original-width="954" height="188" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEid9dAiAFhKjqPVRngDRO3LKMwBAJAYrtbMzbx9c64VM3Tk9SLaX-3sxapyQIQCUekRT2av7KIkV8rolsyzVCXHoCLP_ie6Nl6IuAwiYpiRw2SkSki1pTNqOsXaWpiK7A8LPcX2yFBBlws/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+16.48.03.png" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
次に平面の大きさを調整します。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
デフォルト状態では1m × 1mなので、2m × 2mにします。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Sizeのxとyに2を入力します。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjpdVBvjF2nO89lBxJeDzmsnQxINHtDjOmI_XfYT_lhMbzDWpRFKcREbmxFNUtsE5fz2S7rzN55cwwDqbDP5kIUAIo_KuK7rk6qbcryrKh676hNaStg8r8c383KyBwuYe7cUsw9fyid3w/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+16.49.29.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="784" data-original-width="1411" height="221" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjpdVBvjF2nO89lBxJeDzmsnQxINHtDjOmI_XfYT_lhMbzDWpRFKcREbmxFNUtsE5fz2S7rzN55cwwDqbDP5kIUAIo_KuK7rk6qbcryrKh676hNaStg8r8c383KyBwuYe7cUsw9fyid3w/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+16.49.29.png" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
最後に、平面に影を写すため、Write To Colorの全てのチェックを外しておきます。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h3>
3. Directional Lightを追加する</h3>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<a href="https://www.nsunrise.work/2019/03/arkit_5.html">ライトを追加する方法</a>でも紹介しましたが、ライトのタイプはいくつか存在します。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
影を付けるためには投光器のような一方向の光を上から当てる必要があります。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
投光器のような光のタイプはDirectionalといい、それを追加します。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPJGspx8A63ueYItBzAsuOqH8Q6dDTY_rxoclf8V82gWsu7vmrwDrgdLHw8jqZR2q9oMaTeBKsYwcjYtqhvKGHU18Bnrd-V_tAulm2AfeOW-1PME4Pj6Ry-iyshlmGMmtmYh7NJ31UjRI/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+17.12.16.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="559" data-original-width="882" height="252" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPJGspx8A63ueYItBzAsuOqH8Q6dDTY_rxoclf8V82gWsu7vmrwDrgdLHw8jqZR2q9oMaTeBKsYwcjYtqhvKGHU18Bnrd-V_tAulm2AfeOW-1PME4Pj6Ry-iyshlmGMmtmYh7NJ31UjRI/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+17.12.16.png" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
先ほどと同じようにオブジェクト選択ボタンからDirectional lightを選択し、編集画面上にドラッグ&ドロップします。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3dIU3u-_O5ROxG29JTgh02KjyteUNZmIN9BtbfRMDh9FIVr-QsW4qXX8aoITaiZpKvX_B3d92FMoRP0Jp2xlHo03v3fVvloIQW1_pFaqLMl7s1hgG5uEZCrucYgauj00SXLTzXC7S9tc/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+17.15.03.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="571" data-original-width="1266" height="180" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3dIU3u-_O5ROxG29JTgh02KjyteUNZmIN9BtbfRMDh9FIVr-QsW4qXX8aoITaiZpKvX_B3d92FMoRP0Jp2xlHo03v3fVvloIQW1_pFaqLMl7s1hgG5uEZCrucYgauj00SXLTzXC7S9tc/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+17.15.03.png" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
ライトを配置したら角度と位置を調整します。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
角度はxを-90に設定し、位置は飛行機の上に来るように1を設定します。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhlR_smhxnMcFvmbtyxE5JQbFTBUpa6t_8BAcTRG76JzB1e0GhZWWBoxW-TlHg-GsBpdCvA8kc0CZo_TSvHkV_k4E4uBZClN3v73fomuqvtF2eXw1t9qFaGPzzj1qfsL1MPdWpD_TXtXg/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+17.22.08.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="624" data-original-width="1269" height="196" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhlR_smhxnMcFvmbtyxE5JQbFTBUpa6t_8BAcTRG76JzB1e0GhZWWBoxW-TlHg-GsBpdCvA8kc0CZo_TSvHkV_k4E4uBZClN3v73fomuqvtF2eXw1t9qFaGPzzj1qfsL1MPdWpD_TXtXg/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+17.22.08.png" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
次にshadowの設定を行います。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Casts shadowsにチェックを入れて、ModeをDeferredに設定します。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
また、Castersは"Back face only"に設定をします。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
この設置をしないと光が当たる上面がちらついてしまいます。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjEyjjqa-Nxd8Ebap9cEG68B5YHMD8utdpRukXd413SRvIlcnkUR5qZcn96VQV10XorsHcq09pB9GAxdumBqMwGKjvWF6ykGDKrNpzTL8yRbjK2jo8oEMYeGYgwSvrdjYFHjAtU_Vq9oA/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+17.22.41.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="628" data-original-width="1269" height="197" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjEyjjqa-Nxd8Ebap9cEG68B5YHMD8utdpRukXd413SRvIlcnkUR5qZcn96VQV10XorsHcq09pB9GAxdumBqMwGKjvWF6ykGDKrNpzTL8yRbjK2jo8oEMYeGYgwSvrdjYFHjAtU_Vq9oA/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+17.22.41.png" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
ここまで来れば影が表示されるようになりましたが、影の色が濃すぎるので透過の設定を行います。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
先ほどのshadowのColorを選択しカラーパレットを表示させます。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
黒を選択し、Opacityを50に変更します。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgddJmL19PeyOGK4mKA-clyMSaalStgptv-KuhFnuTe-7nY7WCo2gRc0SeXcn4KxCtYMdY9AkHrCCrxcTKxhZefhuuY1vptdU591-HvvM7uhCXntj5Gys_qyGpIDVz5r32c0nIifiCuqo/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+17.17.40.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="432" data-original-width="585" height="236" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgddJmL19PeyOGK4mKA-clyMSaalStgptv-KuhFnuTe-7nY7WCo2gRc0SeXcn4KxCtYMdY9AkHrCCrxcTKxhZefhuuY1vptdU591-HvvM7uhCXntj5Gys_qyGpIDVz5r32c0nIifiCuqo/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-21+17.17.40.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
最後にライトと影をshipの子ノードに設定をしておきます。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
このようにすることでshipをロードするとライトと影も一緒にロードされます。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h2>
アニメーションのコードをちょっと変更する</h2>
<div>
<br />
<a href="https://www.nsunrise.work/2019/03/arkit_7.html">過去のアニメーションを追加時のコード</a>は次のように書いていましたが、この状態だとライトと影も一緒に回転してしまいます。</div>
<div>
<br /></div>
<pre class="prettyprint swift">if let ship = sceneView.scene.rootNode.childNode(withName: "ship", recursively: true) {
ship.position = targetNode.position
let configuration = ARWorldTrackingConfiguration()
sceneView.session.pause()
sceneView.session.run(configuration)
let completion = {
let rotationAction = SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 0, z: 1, duration: 1))
ship.runAction(rotationAction)
}
SCNTransaction.begin()
SCNTransaction.animationDuration = 1.5
SCNTransaction.completionBlock = completion
ship.opacity = 1
SCNTransaction.commit()
targetNode.isHidden = true
}</pre>
<div>
<br /></div>
<div>
そのため、ライトと影の位置は変えずに飛行機だけを回転させるために次のように変更します。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<pre class="prettyprint swift">if let ship = sceneView.scene.rootNode.childNode(withName: "ship", recursively: true) {
ship.position = targetNode.position
let configuration = ARWorldTrackingConfiguration()
sceneView.session.pause()
sceneView.session.run(configuration)
let completion = {
let rotationAction = SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 0, z: 1, duration: 1))
if let shipMesh = ship.childNode(withName: "shipMesh", recursively: false) {
shipMesh.runAction(rotationAction)
}
}
SCNTransaction.begin()
SCNTransaction.animationDuration = 1.5
SCNTransaction.completionBlock = completion
ship.opacity = 1
SCNTransaction.commit()
targetNode.isHidden = true
}</pre>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h2>
実際の動作</h2>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjb-7joJGkldDshNspdKNT5UUehdkRIkOeB6_bOiQIUdeCx9f0pHbGCbxTEBwF7B8dWMFJVw2NsGnNfa5Hy_6pJh8cUTyswT67tP8YJJ0yctZaGOFAyvhTwv-LH0cD-EskwjmiJvK8jLQE/s1600/Mar-21-2019+22-12-09.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="658" data-original-width="384" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjb-7joJGkldDshNspdKNT5UUehdkRIkOeB6_bOiQIUdeCx9f0pHbGCbxTEBwF7B8dWMFJVw2NsGnNfa5Hy_6pJh8cUTyswT67tP8YJJ0yctZaGOFAyvhTwv-LH0cD-EskwjmiJvK8jLQE/s320/Mar-21-2019+22-12-09.gif" width="186" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<br />
<br /></div>
すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-63649253070290322942019-03-20T00:28:00.000+09:002019-03-21T22:20:03.933+09:00ARKitのサンプルにターゲットカーソルを追加しよう<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkYHKiISQV-6LJMoEJGS-mrCpXZONYHN2SmWRWgg2rpRQ3y1S5EOdzBXIprX-JhQM8twvLiqoLMRNG4Y4NG4LarwFKPskqkn7EWLY2nTjNT7_wFAbYvYzhQkgfSobBVIGhxjjT3D_9e_8/s1600/89867c50a719ffc4f686e8ed787b40f0_m.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1068" data-original-width="1600" height="425" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkYHKiISQV-6LJMoEJGS-mrCpXZONYHN2SmWRWgg2rpRQ3y1S5EOdzBXIprX-JhQM8twvLiqoLMRNG4Y4NG4LarwFKPskqkn7EWLY2nTjNT7_wFAbYvYzhQkgfSobBVIGhxjjT3D_9e_8/s640/89867c50a719ffc4f686e8ed787b40f0_m.jpg" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<h2>
ARKitのサンプルにターゲットカーソルを追加しよう</h2>
<div>
<br /></div>
<div>
ここで取り上げるターゲットカーソルとは公式の計測アプリを起動した時に平面に表示されるカーソルのことです。</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYlzxZdWraxREu1XV2rxGyAoNvj0geLAr59uwoSInE9ECTn15CV3sTP_tNA8zimwoFXRMRZdhoZfd4aPsyV4YfV3zYHYA4FY1AHFPV6jqY21Agvx-vUfoBl_S86TBc3nZdszLPm_HS-G8/s1600/IMG_1690.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="750" data-original-width="1334" height="179" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYlzxZdWraxREu1XV2rxGyAoNvj0geLAr59uwoSInE9ECTn15CV3sTP_tNA8zimwoFXRMRZdhoZfd4aPsyV4YfV3zYHYA4FY1AHFPV6jqY21Agvx-vUfoBl_S86TBc3nZdszLPm_HS-G8/s320/IMG_1690.png" width="320" /></a></div>
<div>
<br /></div>
<div>
このようなカーソルを表示することでユーザーが意図した場所に3Dオブジェクトを置くことができ、ユーザーにとってより使い易いアプリとなります。</div>
<div>
<br /></div>
<div>
<br /></div>
<h2>
ターゲットカーソルの作成</h2>
<div>
<br /></div>
<div>
計測アプリではターゲットカーソルを別途作成してアニメーションを付けていますが、今回は簡略化のためSNCPlaneの形を変えることでターゲットカーソルの代わりとします。</div>
<div>
<br /></div>
<div>
計測アプリのようなターゲットカーソルを作りたい場合は<a href="https://www.nsunrise.work/2019/02/arblender.html">こちらの投稿</a>を参考にしてください。</div>
<div>
<br /></div>
<div>
まず、ターゲットカーソルの可視部分となるジオメトリを定義します。</div>
<div>
<br /></div>
<div>
デフォルトの状態ではSCNPlaneは四角の平面となりますが、cornerRadiusに1を指定することで円形にすることができます。</div>
<div>
<br /></div>
<div>
また、色をつけるためにはdiffuseのcontensに任意のUIColorを設定します。</div>
<div>
<br /></div>
<div>
カーソルの下に何があるか見えた方が良いので半透明にしています。</div>
<div>
<br /></div>
<div>
余談ですが今回色を設定しましたが、画像情報を示すUIImageや2Dグラフィックス情報を示すSKSceneなどを指定することもできます。</div>
<div>
<br /></div>
<pre class="prettyprint swift">
let targetPlane = SCNPlane(width: 0.2, height: 0.2)
targetPlane.cornerRadius = 1
targetPlane.firstMaterial?.diffuse.contents = UIColor.blue.withAlphaComponent(0.5)
</pre>
<div>
作成したジオメトリ情報をSCNNodeに指定することで飛行機と同じようにAR空間で扱うことができるようになります。</div>
<div>
<br /></div>
<div>
尚、targetNodeは別のViewControllerの別メソッド内でも利用したいためメンバ変数として事前に定義をしてあります。<br />
<br />
また、ターゲットカーソルはARSessionが平面を検知したタイミングで表示させたいのでisHiddenはtrueを設定しておきます。</div>
<div>
<br /></div>
<div>
またポイントとしてはデフォルトのSCNPlaneは垂直方向に広がっているため、水平の平面状に表示させるため、x軸を基準に90度回転しておく必要があります。</div>
<pre class="prettyprint swift">
targetNode = SCNNode(geometry: targetPlane)
targetNode.isHidden = true
targetNode.name = "target"
targetNode.eulerAngles.x = -Float.pi / 2
</pre>
<div>
<br /></div>
<h2>
画面の中央の座標を取得する</h2>
<div>
<br /></div>
<div>
ターゲットカーソルはデバイス画面の中央に表示させたいので、画面の中央の座標を取得して値をscreenCenterというメンバ変数として保持しておきます。</div>
<div>
<br /></div>
<pre class="prettyprint swift">
var screenCenter: CGPoint {
let bounds = sceneView.bounds
return CGPoint(x: bounds.midX, y: bounds.midY)
}
</pre>
<div>
<br /></div>
<div>
Swiftではメンバ変数の初期値をクロージャを使って設定することができます。</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<h2>
画面の中央の座標で検知平面のhitTestを行う</h2>
</div>
<div>
<br /></div>
<div>
過去の<a href="https://www.nsunrise.work/2019/03/arkit_10.html">ARKitのサンプルにタッチイベントを追加しよう</a>でも紹介をしましたが、hitTestは3Dオブジェクトだけではなく、ARKitの場合ARKitが検知した平面に対しても行うことができます。</div>
<div>
<br /></div>
<div>
毎フレームごとに画面中央座標で検知平面に対してhitTestを行い、得られた座標にターゲットカーソルを移動させることで、常に画面中央の検知平面上にターゲットカーソルが置かれることになります。</div>
<div>
<br /></div>
<div>
毎フレーム毎の処理は<a href="https://www.nsunrise.work/2019/03/arkit_5.html">ARKitサンプルにライトを追加しよう</a>で取り上げましたが、func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval)というメソッドの中で行います。</div>
<div>
<br /></div>
<div>
メソッドの中に次のようなコードを追加します。</div>
<div>
<br /></div>
<pre class="prettyprint swift">
DispatchQueue.main.async {
let results = self.sceneView.hitTest(self.screenCenter, types: [.existingPlaneUsingGeometry])
if let existingPlaneUsingGeometryResult = results.first(where: { $0.type == .existingPlaneUsingGeometry }) {
let result = existingPlaneUsingGeometryResult
let transform = result.worldTransform
let newPosition = transform.position
self.targetNode.position = newPosition
}
}
</pre>
<div>
<br /></div>
<div>
ここでは下記のような処理を行なっています。</div>
<div>
<br /></div>
<div>
<ol>
<li>画面中央の座標でhitTestを行う</li>
<li>hitTestの結果が検知したものがAR空間上の平面であるかを確認</li>
<li>そうであった場合、平面のhitした座標を取得する</li>
<li>座標を絶対座標に直す</li>
<li>絶対座標が示す場所へtargetNodeを移動させる</li>
</ol>
</div>
<div>
<span style="background-color: white; color: #5c2699; font-family: "menlo"; font-size: 14px;"><br /></span></div>
<div>
<span style="background-color: white; color: #5c2699; font-family: "menlo"; font-size: 14px;">DispatchQueue</span><span style="background-color: white; font-family: "menlo"; font-size: 14px;">.</span><span style="background-color: white; color: #5c2699; font-family: "menlo"; font-size: 14px;">main</span><span style="background-color: white; font-family: "menlo"; font-size: 14px;">.</span><span style="background-color: white; color: #2e0d6e; font-family: "menlo"; font-size: 14px;">async {} </span>と <span style="background-color: white; font-family: "menlo"; font-size: 14px;">transform.</span><span style="background-color: white; color: #3f6e74; font-family: "menlo"; font-size: 14px;">position </span>にはちょっとした理由がありますが、将来の投稿で説明をします。</div>
<div>
<br /></div>
<div>
この時点ではこのように書くと思っておいてください。</div>
<div>
<br /></div>
<div>
<br /></div>
<h2>
平面を検知した際の処理を変更する</h2>
<div>
<br /></div>
<div>
これまでのサンプルアプリでは平面を検知したタイミングで飛行機のオブジェクトをARAnchorに追加をしていましたが、次のように変更をします。</div>
<div>
<br /></div>
<div>
変更前:</div>
<div>
検知した平面を表すARAnchorに飛行機を追加する</div>
<div>
<br /></div>
<div>
変更後:</div>
<div>
画面の初期化時にHiddenに設定したターゲットカーソルを表示する</div>
<div>
<br /></div>
<div>
単純に次のように変更をします。</div>
<div>
<br /></div>
<pre class="prettyprint swift">
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
targetNode.isHidden = false
}
</pre>
<h2>
ターゲットカーソルをタップした時に飛行機を表示させる</h2>
<div>
<br /></div>
<div>
平面を検知した際にターゲットカーソルを表示するため、飛行機を表示させるためのロジックを追加する必要があります。</div>
<div>
<br /></div>
<div>
今回は見出しそのままでターゲットカーソルをユーザーがタップしたら飛行機を表示させるようにします。</div>
<div>
<br /></div>
<div>
<a href="https://www.nsunrise.work/2019/03/arkit_10.html">前回の投稿</a>でhitTestを行なった際に見つかった3Dオブジェクトを名前で判断を行なっていましたが、今回は下記のように"shipMesh"の他にターゲットカーソルを示す"target"という名前の条件判断を入れるようにします。</div>
<div>
<br /></div>
<pre class="prettyprint swift">
if let result = results.first {
guard let hitNodeName = result.node.name else { return }
if hitNodeName == "shipMesh" {
if let ship = result.node.parent {
let actMove = SCNAction.move(by: SCNVector3(0, 0, 0.1), duration: 0.2)
ship.runAction(actMove)
}
} else if hitNodeName == "target" {
.....
}
}
</pre>
<div>
そしてhitTestの結果targetであった場合、これまで行なっていたように飛行機を表示させます。</div>
<div>
<br /></div>
<div>
ただし、注意したいのが今回は飛行機をARAnchorの子ノードとして設定するのではなく、ターゲットカーソルが示す座標に配置するという点です。</div>
<div>
<br /></div>
<div>
イメージとしては次の絵のような感じです。</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9iqEyV7y6Z1zmxm3CV9mTqqUWON9mchwESRta8vzR3MVgopimfWmsZOE7lv0yDparxMNPL_UwDhlz-JKuVVYTTL_X-t-p1cOwmqORuOq_IbLy3oW0JU51xbmsdWtiS0T0rElnlrNetzE/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-19+0.25.12.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="584" data-original-width="1127" height="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9iqEyV7y6Z1zmxm3CV9mTqqUWON9mchwESRta8vzR3MVgopimfWmsZOE7lv0yDparxMNPL_UwDhlz-JKuVVYTTL_X-t-p1cOwmqORuOq_IbLy3oW0JU51xbmsdWtiS0T0rElnlrNetzE/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-19+0.25.12.png" width="400" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
なので次の配置は次のように行います。</div>
<div>
<br /></div>
<pre class="prettyprint swift">
ship.position = targetNode.position
</pre>
<h2>
実際の動作</h2>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJjuyGOQ0pL_exzA6ZVIA1xCcz-34_s45r5XOgE9t9S-JPayzMBaBeRCz3vU4bA6KdtCVo2qsTwVHT3ZX-0C8sYqgs5x1v8MpUULGurKs762EQA_-7i3pQHhQcTfZrV44feMynOjACxFU/s1600/Mar-19-2019+23-28-05.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="658" data-original-width="384" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJjuyGOQ0pL_exzA6ZVIA1xCcz-34_s45r5XOgE9t9S-JPayzMBaBeRCz3vU4bA6KdtCVo2qsTwVHT3ZX-0C8sYqgs5x1v8MpUULGurKs762EQA_-7i3pQHhQcTfZrV44feMynOjACxFU/s320/Mar-19-2019+23-28-05.gif" width="186" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<h2>
修正後のコード</h2>
<div>
<br /></div>
<pre class="prettyprint lang-swift">import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate, UIGestureRecognizerDelegate {
@IBOutlet var sceneView: ARSCNView!
var targetNode : SCNNode!
var screenCenter: CGPoint {
let bounds = sceneView.bounds
return CGPoint(x: bounds.midX, y: bounds.midY)
}
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// Create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
if let ship = scene.rootNode.childNode(withName: "ship", recursively: true) {
if let particle = SCNParticleSystem(named: "BoostFire.scnp", inDirectory: "art.scnassets") {
particle.particleSize = 0.3
if let emitter = ship.childNode(withName: "emitter", recursively: true) {
emitter.addParticleSystem(particle)
}
}
ship.opacity = 0
}
// Create a target
let targetPlane = SCNPlane(width: 0.2, height: 0.2)
targetPlane.cornerRadius = 1
targetPlane.firstMaterial?.diffuse.contents = UIColor.blue.withAlphaComponent(0.5)
targetNode = SCNNode(geometry: targetPlane)
targetNode.isHidden = true
targetNode.name = "target"
targetNode.eulerAngles.x = -Float.pi / 2
scene.rootNode.addChildNode(targetNode)
// Setup Omni Light
let light = SCNLight()
light.type = .omni
let LightNode = SCNNode()
LightNode.light = light
LightNode.name = "light"
LightNode.position = SCNVector3(-1,2,-1)
scene.rootNode.addChildNode(LightNode)
// Set the scene to the view
sceneView.scene = scene
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tap(_:)))
tapRecognizer.delegate = self
sceneView.addGestureRecognizer(tapRecognizer)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = [.horizontal]
// Run the view's session
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
@objc func tap(_ tapRecognizer: UITapGestureRecognizer) {
let touchPoint = tapRecognizer.location(in: self.sceneView)
let results = self.sceneView.hitTest(touchPoint, options: [SCNHitTestOption.searchMode : SCNHitTestSearchMode.all.rawValue])
if let result = results.first {
guard let hitNodeName = result.node.name else { return }
if hitNodeName == "shipMesh" {
if let ship = result.node.parent {
let actMove = SCNAction.move(by: SCNVector3(0, 0, 0.1), duration: 0.2)
ship.runAction(actMove)
}
} else if hitNodeName == "target" {
if let ship = sceneView.scene.rootNode.childNode(withName: "ship", recursively: true) {
ship.position = targetNode.position
let configuration = ARWorldTrackingConfiguration()
sceneView.session.pause()
sceneView.session.run(configuration)
let completion = {
let rotationAction = SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 0, z: 1, duration: 1))
ship.runAction(rotationAction)
}
SCNTransaction.begin()
SCNTransaction.animationDuration = 1.5
SCNTransaction.completionBlock = completion
ship.opacity = 1
SCNTransaction.commit()
targetNode.isHidden = true
}
}
}
}
// MARK: - ARSCNViewDelegate
/*
// Override to create and configure nodes for anchors added to the view's session.
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
let node = SCNNode()
return node
}
*/
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
targetNode.isHidden = false
}
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
guard let lightEstimate = self.sceneView.session.currentFrame?.lightEstimate else { return }
let ambientIntensity = lightEstimate.ambientIntensity
let ambientColorTemperature = lightEstimate.ambientColorTemperature
if let lightNode = self.sceneView.scene.rootNode.childNode(withName: "light", recursively: true) {
guard let light = lightNode.light else { return }
light.intensity = ambientIntensity
light.temperature = ambientColorTemperature
}
DispatchQueue.main.async {
let results = self.sceneView.hitTest(self.screenCenter, types: [.existingPlaneUsingGeometry])
if let existingPlaneUsingGeometryResult = results.first(where: { $0.type == .existingPlaneUsingGeometry }) {
let result = existingPlaneUsingGeometryResult
let transform = result.worldTransform
let newPosition = transform.position
self.targetNode.position = newPosition
}
}
}
func session(_ session: ARSession, didFailWithError error: Error) {
// Present an error message to the user
}
func sessionWasInterrupted(_ session: ARSession) {
// Inform the user that the session has been interrupted, for example, by presenting an overlay
}
func sessionInterruptionEnded(_ session: ARSession) {
// Reset tracking and/or remove existing anchors if consistent tracking is required
}
}
extension matrix_float4x4 {
var position : SCNVector3 {
get {
return SCNVector3(columns.3.x, columns.3.y, columns.3.z)
}
}
}
</pre>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-55376708945883072932019-03-18T00:58:00.000+09:002019-03-18T00:58:25.411+09:00iOSのAdMob(Mobile Ads SDK)でGADInvalidInitializationException<h2>
appIDの設定方法が変わっていた</h2>
<div>
<br /></div>
開発途中のアプリにAdMobの広告を入れようと思い、過去のアプリと同じ手順でバナー広告を入れたところ次のようなエラーが起きた。<br />
<div>
<br /></div>
<blockquote class="tr_bq">
<div style="background-color: white; font-family: Menlo; font-size: 11px; font-stretch: normal; line-height: normal;">
<b>*** Terminating app due to uncaught exception 'GADInvalidInitializationException', reason: 'The Google Mobile Ads SDK was initialized incorrectly. Google AdMob publishers should follow instructions here: https://googlemobileadssdk.page.link/admob-ios-update-plist to include the AppMeasurement framework, set the -ObjC linker flag, and set GADApplicationIdentifier with a valid App ID. Google Ad Manager publishers should follow instructions here: https://googlemobileadssdk.page.link/ad-manager-ios-update-plist'</b></div>
</blockquote>
<br />
<a href="https://developers.google.com/admob/ios/quick-start">公式のページ</a>通りに設定をしているのになぜか上手くいかない。<br />
<br />
どうやらSDKのバージョンが上がってappIDの設定方法が変わったらしい。<br />
<br />
最新版のバージョンではAppDelegateの起動時にappIDの設定を行うのではなく、Info.plistに記述するとのこと。<br />
<br />
※その代わりにAppDelegateの起動時に<span style="background-color: white; color: #3f6e74; font-family: "menlo"; font-size: 14px;">GADMobileAds</span><span style="background-color: white; font-family: "menlo"; font-size: 14px;">.</span><span style="background-color: white; color: #26474b; font-family: "menlo"; font-size: 14px;">sharedInstance</span><span style="background-color: white; font-family: "menlo"; font-size: 14px;">().</span><span style="background-color: white; color: #26474b; font-family: "menlo"; font-size: 14px;">start</span><span style="background-color: white; font-family: "menlo"; font-size: 14px;">(completionHandler: </span><span style="background-color: white; color: #aa0d91; font-family: "menlo"; font-size: 14px;">nil</span><span style="background-color: white; font-family: "menlo"; font-size: 14px;">)</span>を実行する必要がある<br />
<br />
<br />
公式ページ の日本語版はまだ説明が追いついていないようで、英語版だとInfo.plistに書くようにとの説明になっていた。<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBxTJLbjYib4hErxRCTklh09xiDFDpbYXqoVfH__CQdF4dUwxhVxdRBpCAepj1H62NxPuf6OSF0HMxjrV19y-S8lf00tEIJiEjwMsDxD6Dr8BI-w0M_YI5tNO4u99rm8xbae3vg5B_k8A/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-18+0.50.39.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="808" data-original-width="876" height="295" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBxTJLbjYib4hErxRCTklh09xiDFDpbYXqoVfH__CQdF4dUwxhVxdRBpCAepj1H62NxPuf6OSF0HMxjrV19y-S8lf00tEIJiEjwMsDxD6Dr8BI-w0M_YI5tNO4u99rm8xbae3vg5B_k8A/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-18+0.50.39.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">日本語版<br /></td></tr>
</tbody></table>
<div>
<br /></div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuUu0_dD1JiZAwteXnMjabARlMlTBv8FIJWEAck5pwjdbK-LP6v3k9wfuieVvk17EtLHOc6tcG2p5axukHKRNaFO00JfK_dJM7aJMV2MOC6olJ9mh_yfykwGLilWvml7Wn1DiA4enkBgI/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-18+0.51.52.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="769" data-original-width="872" height="282" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuUu0_dD1JiZAwteXnMjabARlMlTBv8FIJWEAck5pwjdbK-LP6v3k9wfuieVvk17EtLHOc6tcG2p5axukHKRNaFO00JfK_dJM7aJMV2MOC6olJ9mh_yfykwGLilWvml7Wn1DiA4enkBgI/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-18+0.51.52.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">英語版</td></tr>
</tbody></table>
<div>
<br /><br /></div>
すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-22643974827748987272019-03-13T22:45:00.000+09:002019-03-20T16:28:04.878+09:00ARKitサンプルにジェットのパーティクルを追加しよう<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyajzfOcCt1cjnWZTQIqDM1OOvERGf6JCb2PorqLWRJFlopfE7t8XsxvNIE9NCdEtJfstacRtfJYd_tTs-4td1x5mKFxiHfJBLV0h_b1rDA3JErRF6H5hPlq9X9ubNdiD2ZGsb7i_rBko/s1600/2da4b3673da5ae82963bb40e7a8325e8_s.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="427" data-original-width="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyajzfOcCt1cjnWZTQIqDM1OOvERGf6JCb2PorqLWRJFlopfE7t8XsxvNIE9NCdEtJfstacRtfJYd_tTs-4td1x5mKFxiHfJBLV0h_b1rDA3JErRF6H5hPlq9X9ubNdiD2ZGsb7i_rBko/s1600/2da4b3673da5ae82963bb40e7a8325e8_s.jpg" /></a></div>
<br />
<br />
<h2>
ARKitサンプルにジェットのパーティクルを追加しよう</h2>
<div>
<br /></div>
<div>
<br /></div>
<div>
ARKitのサンプル飛行機に下の絵のようなジェットのパーティクルを追加してみましょう。</div>
<div>
<br /></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFh5K3B3PDq6BYtflq6yh4nEN9-wmu1RMbyaSe7VUdzAUj9WDx1RFnkyv3JAPvn7yb1kEiayXpiae6AcazQe4efVfASwn9zhLhBD5aflUb241c8so7GF8rLtAaU_WPjsMMIUNjCoLwqaI/s1600/IMG_0046.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1200" data-original-width="1600" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFh5K3B3PDq6BYtflq6yh4nEN9-wmu1RMbyaSe7VUdzAUj9WDx1RFnkyv3JAPvn7yb1kEiayXpiae6AcazQe4efVfASwn9zhLhBD5aflUb241c8so7GF8rLtAaU_WPjsMMIUNjCoLwqaI/s400/IMG_0046.PNG" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div>
パーティクルとは小さな画像を粒子に見立てて、加速度や出現頻度、範囲などの粒子運動を指定することで炎や煙、雪などをシミュレートするコンピューターグラフィックス技術です。</div>
<div>
<br /></div>
<div>
Web上でパーティクルを作成できる<a href="http://particle2dx.com/">Particle2dx</a>のようなサービスもありますが、Xcodeでも標準でパーティクルを作成することができます。</div>
<div>
<br /></div>
<div>
<br /></div>
<h2>
ジェットのパーティクルを作る</h2>
<div>
<br /></div>
<div>
まず始めにXcodeの左のペインより適当な場所を選択し、右クリックで「New File...」を選択します。</div>
<div>
<br /></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCjJA4TUf5csOaMVnPf3sAMLj5UM5U3B5to6xsUdSvMzDmjMAxSK0gbZywXclZtgGd-XQZmTrVeFyExylG0KwUJKj3yjKtFuDjEe_hyphenhyphen6uX1inzFV5C1UCKpAVxu60_abRrXxmK3z7xKQg/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-12+23.05.39.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="678" data-original-width="724" height="299" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCjJA4TUf5csOaMVnPf3sAMLj5UM5U3B5to6xsUdSvMzDmjMAxSK0gbZywXclZtgGd-XQZmTrVeFyExylG0KwUJKj3yjKtFuDjEe_hyphenhyphen6uX1inzFV5C1UCKpAVxu60_abRrXxmK3z7xKQg/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-12+23.05.39.png" width="320" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
作成するファイル形式の一覧が表示されますので、その中から「SceneKit Particle System File」を選択します。</div>
<div>
<br /></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkBxhxF0_ODxiuJtCeyjXuSgCYp-xyhBpccEcpj6hAGX72dolPs09vpIT9EytgW-I_s1f7MHbqn8pSgbZEii8OANMhFp2yEm7aNyralUEZ8qxgtMgAfvH4Bq8BqaKDsLlsb3hR5-oOBQc/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-12+23.05.58.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="541" data-original-width="744" height="232" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkBxhxF0_ODxiuJtCeyjXuSgCYp-xyhBpccEcpj6hAGX72dolPs09vpIT9EytgW-I_s1f7MHbqn8pSgbZEii8OANMhFp2yEm7aNyralUEZ8qxgtMgAfvH4Bq8BqaKDsLlsb3hR5-oOBQc/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-12+23.05.58.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Particle System Template の確認がありますがここはSmokeを選びましょう。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
ファイル名はBoostFireという名前を指定します。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
BoostFire.scnptというファイルが作成されるので、それを選択するとパーティクルの編集を行うことができます。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
様々なパラメータがあって迷ってしまいますが、下記のような値を設定してください。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh39h-HxFeu_Qzimrhi4GNUYSXkvtVwfCkN6jp1ZE7WXLqXSeWdyJvdgcXtNLybt6lNQilXoQzgosJawpPGkfgjR-3vl-f-2yjMa1OqcKXwZgcAEieMNaxIp-K-YgZ5Vs0IFudUw7UitSs/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-13+21.26.31.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1156" data-original-width="825" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh39h-HxFeu_Qzimrhi4GNUYSXkvtVwfCkN6jp1ZE7WXLqXSeWdyJvdgcXtNLybt6lNQilXoQzgosJawpPGkfgjR-3vl-f-2yjMa1OqcKXwZgcAEieMNaxIp-K-YgZ5Vs0IFudUw7UitSs/s640/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-13+21.26.31.png" width="456" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
炎を演出するために重要なのは赤枠で囲った部分です。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<ul>
<li>Acceleration → y の値に1をセットすることで炎の勢いを演出することができます</li>
<li>Color → 色を指定することで炎の赤を演出することができます</li>
<li>Custom Animation → 炎の形を作ることができます</li>
</ul>
<div>
<br /></div>
<div>
Custom Animationの部分はパーティクルの広がり方を指定します。</div>
<div>
<br /></div>
<div>
デフォルトの状態では上に登るに従って発散する形なので右肩上がりの線になりますが、始まりから一度膨らんで萎む炎の形は下の絵のように山形になります。</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLk8zPebxySHdyEAsFIxGl6fS9Ls-mALwJnl9Sa-27N-uwf8yDfxQHyovehh6xWbYff-9IAkCytRzepS0M-5Vg-TdSPIWnrhuhm0aQJvOz6580gXYOQTb1fRMuV6MrwQtLWMlHpWucLio/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-13+21.28.17.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="424" data-original-width="352" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLk8zPebxySHdyEAsFIxGl6fS9Ls-mALwJnl9Sa-27N-uwf8yDfxQHyovehh6xWbYff-9IAkCytRzepS0M-5Vg-TdSPIWnrhuhm0aQJvOz6580gXYOQTb1fRMuV6MrwQtLWMlHpWucLio/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-13+21.28.17.png" width="265" /></a></div>
<div>
<br /></div>
<br />
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h2>
パーティクルを飛行機に追加する</h2>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
パーティクルが作れたので、早速飛行機に追加しましょう。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
追加する場所は飛行機のお尻部分になります。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
自作の3Dオブジェクトであれば追加したい場所に空のノードを作成しておく必要がありますが、サンプルの飛行機には既にemitterというノードが用意されています。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZzmpOlgLFWeq2mRH9U9iADeEoF7wAk9ZrO5k4I9EibnLW6q2sq1cK3EBPblnsXmieScN5YLsJ_292MPunnf7lY-rVULROyrG8ICXghFyGE-cZNJReHPfH3kbXnmc1fMg4AgN5ASbZFOw/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-13+21.22.53.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="278" data-original-width="565" height="313" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZzmpOlgLFWeq2mRH9U9iADeEoF7wAk9ZrO5k4I9EibnLW6q2sq1cK3EBPblnsXmieScN5YLsJ_292MPunnf7lY-rVULROyrG8ICXghFyGE-cZNJReHPfH3kbXnmc1fMg4AgN5ASbZFOw/s640/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-13+21.22.53.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
コード上の追加はとても簡単ですが、追加時にディレクトリを指定する必要があるので先に先程作成したBoostFire.scnpとsmoke.pngをart.scnassetsへ移動しておきましょう。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDh_fv3vZw1rospOKFoA375AGruABVdShNJv8CpghetMjdg6Ql47mRKMxjW4lwnKZczXsNq-TSxDkUtfDWf6u99X4EKaMe4k8fesT_Ld3xbi5qFaaVtkU_PkFIz7mbvFwdw7PSdf7rans/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-13+22.32.49.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="290" data-original-width="221" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDh_fv3vZw1rospOKFoA375AGruABVdShNJv8CpghetMjdg6Ql47mRKMxjW4lwnKZczXsNq-TSxDkUtfDWf6u99X4EKaMe4k8fesT_Ld3xbi5qFaaVtkU_PkFIz7mbvFwdw7PSdf7rans/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-13+22.32.49.png" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div>
<br /></div>
<div>
移動後はこのようなフォルダ構成になるはずです。</div>
<div>
<br /></div>
<div>
次にViewController.swiftを開き、ViewDidLoad()内でshipをロードしている箇所に次のコードを追加します。</div>
<div>
<br /></div>
<pre class="prettyprint swift">
if let particle = SCNParticleSystem(named: "BoostFire.scnp", inDirectory: "art.scnassets") {
particle.particleSize = 0.3
if let emitter = ship.childNode(withName: "emitter", recursively: true) {
emitter.addParticleSystem(particle)
}
}
</pre>
<div>
<br /></div>
<div>
このようにすることでファイルからロードされたパーティクルがemitterの場所に配置されます。</div>
<div>
<br /></div>
<div>
<br /></div>
<h2>
実際の動作</h2>
<div>
<br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPUF22_MOD4XJpDwZPDmEABlaEhuoLO3HTGnvhjCGRQB8YRQcn6FsoZhnq7zKH2WvOcM3Yidh5vn9ATu51F-1ZczVVWq1QuPi6IDgWJuhJclT52UkK3DqpgMc2X039ZNlnldfVFnEfstg/s1600/Mar-13-2019+22-43-03.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="658" data-original-width="384" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPUF22_MOD4XJpDwZPDmEABlaEhuoLO3HTGnvhjCGRQB8YRQcn6FsoZhnq7zKH2WvOcM3Yidh5vn9ATu51F-1ZczVVWq1QuPi6IDgWJuhJclT52UkK3DqpgMc2X039ZNlnldfVFnEfstg/s320/Mar-13-2019+22-43-03.gif" width="186" /></a></div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<h2>
修正後のコード</h2>
<pre class="prettyprint lang-swift">import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate, UIGestureRecognizerDelegate {
@IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// Create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
if let ship = scene.rootNode.childNode(withName: "ship", recursively: true) {
if let particle = SCNParticleSystem(named: "BoostFire.scnp", inDirectory: "art.scnassets") {
particle.particleSize = 0.3
if let emitter = ship.childNode(withName: "emitter", recursively: true) {
emitter.addParticleSystem(particle)
}
}
ship.opacity = 0
}
// Setup Omni Light
let light = SCNLight()
light.type = .omni
let LightNode = SCNNode()
LightNode.light = light
LightNode.name = "light"
LightNode.position = SCNVector3(-1,2,-1)
scene.rootNode.addChildNode(LightNode)
// Set the scene to the view
sceneView.scene = scene
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tap(_:)))
tapRecognizer.delegate = self
sceneView.addGestureRecognizer(tapRecognizer)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = [.horizontal]
// Run the view's session
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
@objc func tap(_ tapRecognizer: UITapGestureRecognizer) {
let touchPoint = tapRecognizer.location(in: self.sceneView)
let results = self.sceneView.hitTest(touchPoint, options: [SCNHitTestOption.searchMode : SCNHitTestSearchMode.all.rawValue])
if let result = results.first {
guard let hitNodeName = result.node.name else { return }
guard hitNodeName == "shipMesh" else { return }
if let ship = result.node.parent {
let actMove = SCNAction.move(by: SCNVector3(0, 0, 0.1), duration: 0.2)
ship.runAction(actMove)
}
}
}
// MARK: - ARSCNViewDelegate
/*
// Override to create and configure nodes for anchors added to the view's session.
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
let node = SCNNode()
return node
}
*/
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
if let ship = sceneView.scene.rootNode.childNode(withName: "ship", recursively: true) {
ship.removeFromParentNode()
node.addChildNode(ship)
let configuration = ARWorldTrackingConfiguration()
sceneView.session.pause()
sceneView.session.run(configuration)
let completion = {
let rotationAction = SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 0, z: 1, duration: 1))
ship.runAction(rotationAction)
}
SCNTransaction.begin()
SCNTransaction.animationDuration = 1.5
SCNTransaction.completionBlock = completion
ship.opacity = 1
SCNTransaction.commit()
}
}
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
guard let lightEstimate = self.sceneView.session.currentFrame?.lightEstimate else { return }
let ambientIntensity = lightEstimate.ambientIntensity
let ambientColorTemperature = lightEstimate.ambientColorTemperature
if let lightNode = self.sceneView.scene.rootNode.childNode(withName: "light", recursively: true) {
guard let light = lightNode.light else { return }
light.intensity = ambientIntensity
light.temperature = ambientColorTemperature
}
}
func session(_ session: ARSession, didFailWithError error: Error) {
// Present an error message to the user
}
func sessionWasInterrupted(_ session: ARSession) {
// Inform the user that the session has been interrupted, for example, by presenting an overlay
}
func sessionInterruptionEnded(_ session: ARSession) {
// Reset tracking and/or remove existing anchors if consistent tracking is required
}
}</pre>
<div>
<br /></div>
<div>
<br /></div>すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-52153998992547520152019-03-10T00:34:00.002+09:002019-03-20T16:26:21.293+09:00ARKitのサンプルにタッチイベントを追加しよう<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmq9lFGA7JQnSpfJqPTmtiAoAUaBGSMNso7y_wv6Gt7owd5PRR0Uwuy-k9n7fgWIYeyFPVAJwE5LHbJc31fyswBhvoFVbYg_B6gyxErYH8su60jW2aQdu-MCd5dcDQD0glCrRPv6wY9pY/s1600/73aa70e679b69bcbc565f39efba71433_s.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="389" data-original-width="640" height="389" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmq9lFGA7JQnSpfJqPTmtiAoAUaBGSMNso7y_wv6Gt7owd5PRR0Uwuy-k9n7fgWIYeyFPVAJwE5LHbJc31fyswBhvoFVbYg_B6gyxErYH8su60jW2aQdu-MCd5dcDQD0glCrRPv6wY9pY/s640/73aa70e679b69bcbc565f39efba71433_s.jpg" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<h2>
ARKitのサンプルにタッチイベントを追加しよう</h2>
<div>
<br /></div>
<div>
インタラクティブなアプリケーションを作る場合にはユーザーの操作を処理する必要が出てきます。</div>
<div>
<br /></div>
<div>
通常のアプリではボタンなどのUI部品それぞれにイベントハンドラを仕込むことができるので、何がユーザーによって押されたかを調べる必要はありません。<br />
<br />
しかし、ARKitの場合オブジェクトそれぞれにイベントハンドラを仕込むことはできないため、hitTestを使ってどのオブジェクトが押されたのかを調べる必要があります。</div>
<div>
<br /></div>
<div>
<br /></div>
<h2>
hitTestとは</h2>
<div>
<br /></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzWx_uJGc5Y-lCbFw37jMbU5_KZQm8_B9f2Raf1wRHsL8IpWVINgsPmiU9fLYG97D5To9Gc9rnOx7nx2WSHLHwbTj7fHTlFHppqjX0rRiEcskd1qzrZSSr0DUCd2j7nDVwJpfYbiPgj08/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-09+12.26.34.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="427" data-original-width="735" height="231" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzWx_uJGc5Y-lCbFw37jMbU5_KZQm8_B9f2Raf1wRHsL8IpWVINgsPmiU9fLYG97D5To9Gc9rnOx7nx2WSHLHwbTj7fHTlFHppqjX0rRiEcskd1qzrZSSr0DUCd2j7nDVwJpfYbiPgj08/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-09+12.26.34.png" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div>
<br /></div>
<div>
hitTestとは、ある特定の座標から直線を伸ばしたときに、その延長線上にオブジェクトがあるかを確認する方法です。<br />
<br />
ユーザーがタッチした座標でhitTestを行うことで何が選択されたのかを知ることができます。<br />
<br />
そして、ARKitの場合hitTestで調べられるオブジェクトは主に2つあります。<br />
<br />
<h3>
</h3>
<h4>
<ul>
<li>平面検知や画像認識によって置かれたARAnchor</li>
<li>AR空間上に置かれた3Dオブジェクト(例:サンプルアプリの飛行機)</li>
</ul>
</h4>
<div>
<br /></div>
<div>
今回は後者を扱いますが、次回以降で検知した平面をhitTestする方法を紹介したいと思います。</div>
</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<h2>
修正方針</h2>
<br /></div>
<div>
今回は次のようにアプリを修正してみたいと思います。<br />
<br />
<br />
<ul>
<li>画面のタップイベントを拾う</li>
<li>タップ座標を取得する</li>
<li>座標情報を元にhitTestを行う</li>
<li>飛行機がタップされたのであれば10cm前進させる</li>
</ul>
<br />
<br /></div>
<div>
<h3>
画面のタップイベントを拾う</h3>
</div>
<div>
<br /></div>
<div>
画面のタップイベントを拾うためにはメインのViewControllerがGestureRecognizerのdelegateになる必要があります。</div>
<div>
<br /></div>
<div>
クラス定義に下記のDelegateを追加しましょう。</div>
<div>
<br /></div>
<pre class="prettyprint swift">
UIGestureRecognizerDelegate
</pre>
<div>
<br /></div>
<div>
また、画面(sceneView)がタップされたことを検知したいので、TapGestureRecognizerを生成し、イベント処理者をViewControllerに設定し、sceneViewに登録をしておきます。</div>
<div>
<br /></div>
<pre class="prettyprint swift">
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tap(_:)))
tapRecognizer.delegate = self
sceneView.addGestureRecognizer(tapRecognizer)
</pre>
<div>
UITapGestureRecognizerのtarget引数がイベント処理者を指定し、actionが呼ばれるメソッドを指定します。</div>
<div>
<br /></div>
<div>
そのため、呼ばれるメソッドをViewControllerにtapという名前で用意しておきます。</div>
<div>
<br /></div>
<pre class="prettyprint swift">
@objc func tap(_ tapRecognizer: UITapGestureRecognizer) {
}
</pre>
<h3>
タップ座標を取得する</h3>
<div>
<br /></div>
<div>
画面をタップした時にtap()が呼ばれるようになったので、tap()の中に次のようにタップした座標を取得するメソッドを追加します。</div>
<div>
<br /></div>
<pre class="prettyprint swift">
let touchPoint = tapRecognizer.location(in: self.sceneView)
</pre>
<div>
<br /></div>
<h3>
座標情報を基にhitTestを行う</h3>
<div>
<br /></div>
<div>
次のようにhitTestを呼び出し、touchPointを渡してやります。</div>
<div>
<br /></div>
<pre class="prettyprint swift">
let results = self.sceneView.hitTest(touchPoint, options: [SCNHitTestOption.searchMode : SCNHitTestSearchMode.all.rawValue])
</pre>
<div>
<br /></div>
<div>
option引数にパラメータを仕込むことで、細かな制御ができますが、今回はシンプルにhitTestの延長線上の全てのオブジェクトを検知対象とするようにします。</div>
<div>
<br /></div>
<div>
<br />
<br /></div>
<h3>
飛行機がタップされたのであれば10cm前進させる</h3>
<div>
<br /></div>
<div>
飛行機がタップされたことは検知ノードの名前を調べることで判断することができます。</div>
<div>
<br /></div>
<div>
また、このときに注意して欲しいのがhitTestの当たり判定は延長線上にある全てのSCNNodeを引っ張ってくるため、必然的に当たり判定は3Dデータのメッシュを表すノードが判定され易くなります。</div>
<div>
<br /></div>
<div>
サンプルの飛行機データはshipというノード配下の子ノードにshipMeshというデータがあり、これがメッシュデータを表すノードになります。<br />
<br />
<pre><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGpSlzcraYR0o69RvwcYTSaJewQreOlmG0j_hr2JrvjVg1yhDaI_OoH4C-Ks838zSNMVdhy-1_3MprzshXrBRq6RSdyIyCX0deE1zhQksFjJSwHpGzme7QMKv7WEIFwsM7sW7WvY6ylV8/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-10+0.32.53.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="308" data-original-width="762" height="161" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGpSlzcraYR0o69RvwcYTSaJewQreOlmG0j_hr2JrvjVg1yhDaI_OoH4C-Ks838zSNMVdhy-1_3MprzshXrBRq6RSdyIyCX0deE1zhQksFjJSwHpGzme7QMKv7WEIFwsM7sW7WvY6ylV8/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-10+0.32.53.png" width="400" /></a></div>
</pre>
</div>
<div>
<br /></div>
<div>
shipノードの当たり判定がない訳ではありませんが、非常に小さいためユーザーにタップされたことを検知するには子ノードのshipMeshで判定した方が、ユーザーの期待値に沿うことになります。</div>
<div>
<br /></div>
<pre class="prettyprint swift">
if let result = results.first {
guard let hitNodeName = result.node.name else { return }
guard hitNodeName == "shipMesh" else { return }
if let ship = result.node.parent {
let actMove = SCNAction.move(by: SCNVector3(0, 0, 0.1), duration: 0.2)
ship.runAction(actMove)
}
}
</pre>
<div>
<br /></div>
<div>
<br /></div>
<h2>
実際の動作</h2>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqygax5BUcjctSLbum00CztzkXLq_VgatfbHMFJNk0dDhrs_2dSX5HEeJcdTMD1izR5FuHgDnVSUcdk5wfGk2VqqA5g4ofS529pCvvvihd1EIKX9hIUZrfvtthCvlBJmd88A8TTyUTlRY/s1600/Mar-09-2019+23-41-28.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="658" data-original-width="384" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqygax5BUcjctSLbum00CztzkXLq_VgatfbHMFJNk0dDhrs_2dSX5HEeJcdTMD1izR5FuHgDnVSUcdk5wfGk2VqqA5g4ofS529pCvvvihd1EIKX9hIUZrfvtthCvlBJmd88A8TTyUTlRY/s320/Mar-09-2019+23-41-28.gif" width="186" /></a></div>
<div>
<br /></div>
<div>
<br />
<br /></div>
<h2>
修正後のソースコード</h2>
<div>
<br /></div>
<div>
<pre class="prettyprint lang-swift">import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate, UIGestureRecognizerDelegate {
@IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// Create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
if let ship = scene.rootNode.childNode(withName: "ship", recursively: true) {
ship.opacity = 0
}
// Setup Omni Light
let light = SCNLight()
light.type = .omni
let LightNode = SCNNode()
LightNode.light = light
LightNode.name = "light"
LightNode.position = SCNVector3(-1,2,-1)
scene.rootNode.addChildNode(LightNode)
// Set the scene to the view
sceneView.scene = scene
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tap(_:)))
tapRecognizer.delegate = self
sceneView.addGestureRecognizer(tapRecognizer)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = [.horizontal]
// Run the view's session
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
@objc func tap(_ tapRecognizer: UITapGestureRecognizer) {
let touchPoint = tapRecognizer.location(in: self.sceneView)
let results = self.sceneView.hitTest(touchPoint, options: [SCNHitTestOption.searchMode : SCNHitTestSearchMode.all.rawValue])
if let result = results.first {
guard let hitNodeName = result.node.name else { return }
guard hitNodeName == "shipMesh" else { return }
if let ship = result.node.parent {
let actMove = SCNAction.move(by: SCNVector3(0, 0, 0.1), duration: 0.2)
ship.runAction(actMove)
}
}
}
// MARK: - ARSCNViewDelegate
/*
// Override to create and configure nodes for anchors added to the view's session.
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
let node = SCNNode()
return node
}
*/
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
if let ship = sceneView.scene.rootNode.childNode(withName: "ship", recursively: true) {
ship.removeFromParentNode()
node.addChildNode(ship)
let configuration = ARWorldTrackingConfiguration()
sceneView.session.pause()
sceneView.session.run(configuration)
let completion = {
let rotationAction = SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 0, z: 1, duration: 1))
ship.runAction(rotationAction)
}
SCNTransaction.begin()
SCNTransaction.animationDuration = 1.5
SCNTransaction.completionBlock = completion
ship.opacity = 1
SCNTransaction.commit()
}
}
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
guard let lightEstimate = self.sceneView.session.currentFrame?.lightEstimate else { return }
let ambientIntensity = lightEstimate.ambientIntensity
let ambientColorTemperature = lightEstimate.ambientColorTemperature
if let lightNode = self.sceneView.scene.rootNode.childNode(withName: "light", recursively: true) {
guard let light = lightNode.light else { return }
light.intensity = ambientIntensity
light.temperature = ambientColorTemperature
}
}
func session(_ session: ARSession, didFailWithError error: Error) {
// Present an error message to the user
}
func sessionWasInterrupted(_ session: ARSession) {
// Inform the user that the session has been interrupted, for example, by presenting an overlay
}
func sessionInterruptionEnded(_ session: ARSession) {
// Reset tracking and/or remove existing anchors if consistent tracking is required
}
}
</pre>
</div>すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-57319424938132091672019-03-07T23:43:00.002+09:002019-03-20T16:16:52.180+09:00ARKitのサンプルにアニメーションを追加しよう<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoVbIBwxqQVbPAOeZjUJY-piMi3ovUkvXimWLZikh_WpUQcsI0_oTyzso78hppwewms-rlOTlhSP57bC11xLv5W1lRp8yvYBS-Li-Zb3Ld3OR7dBJpf5klcmZt4Ery3IvoRL3ZPOf2Qfc/s1600/9d39bec6eeba50f7b993e8b5c2b679f1_s.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="427" data-original-width="640" height="427" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoVbIBwxqQVbPAOeZjUJY-piMi3ovUkvXimWLZikh_WpUQcsI0_oTyzso78hppwewms-rlOTlhSP57bC11xLv5W1lRp8yvYBS-Li-Zb3Ld3OR7dBJpf5klcmZt4Ery3IvoRL3ZPOf2Qfc/s640/9d39bec6eeba50f7b993e8b5c2b679f1_s.jpg" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h2>
ARKitのサンプルにアニメーションを追加しよう</h2>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
今回扱うのはアニメーションです。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
ただAR空間の物体に動きが入るとよりリアルさが出て楽しくなります。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h2>
アニメーションの設定方法</h2>
<div>
<br /></div>
<div>
アニメーションの設定方法は大きく3つあります。</div>
<div>
<br /></div>
<div>
<h4>
</h4>
<h4>
</h4>
<h4>
<ul>
<li><span style="font-weight: normal;">SCNAction</span></li>
</ul>
<ul>
<li><span style="font-weight: normal;">SCNTransaction</span></li>
</ul>
<ul>
<li><span style="font-weight: normal;">CoreAnimation</span></li>
</ul>
</h4>
<div>
<br /></div>
<h3>
SCNAction</h3>
</div>
<div>
<br /></div>
<div>
直進や回転、拡大縮小、フェードイン/フェードアウトをそれぞれ対応したActionを使ってAR上の物体にアニメーションを適用することができます。</div>
<div>
<br /></div>
<div>
<br /></div>
<h3>
SCNTransaction</h3>
<div>
<br /></div>
<div>
AR空間の物体は60FPSのループで常に描画されていますが、その間隔を伸ばすことによりアニメーションを設定することができます。</div>
<div>
<br /></div>
<div>
イメージとしては物体を1m右に動かすことを1秒かけて行うことで、その間の動きが描画される感じです。</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3dRFi27EGB5RVaMWlklLCZPhaGwguIKZty5ym5mSJYgGGzRJjux_jhbCMHNxItG7M4UBW2AbhZb09f2RNo5P3S4QguUdUql7ew0fcAzghZCK0mjF51kFMEAmQBvgJXYQyv0NC8kRQXiE/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-07+22.48.11.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="644" data-original-width="927" height="277" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3dRFi27EGB5RVaMWlklLCZPhaGwguIKZty5ym5mSJYgGGzRJjux_jhbCMHNxItG7M4UBW2AbhZb09f2RNo5P3S4QguUdUql7ew0fcAzghZCK0mjF51kFMEAmQBvgJXYQyv0NC8kRQXiE/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-07+22.48.11.png" width="400" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<h3>
CoreAnimation</h3>
<div>
<br /></div>
<div>
通常AR空間に表示する3DオブジェクトはBlenderなどの3Dモデリングツールを使って作られますが、3Dモデリングツールでは形を作るだけでなくオブジェクトにアニメーションを設定することができます。</div>
<div>
<br /></div>
<div>
そのような、外部ツールで設定されたアニメーションを呼び出すことでアニメーションを描画することができます。</div>
<div>
<br /></div>
<div>
キャラクターが歩くなどの複雑なアニメーションの場合は大抵CoreAnimationを使います。</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSCkxz1jY9AHeFawLaxcNYIDAQcTSZL_AvCXKiKzU_j4FGufYcp16_lN6pumVidJWYgXI9LDZF-K8IN1ksXtn58Km2ROx418PjobW7LOVp14slldxPLcWC33ubo6-tWYN87skEBuKCQp8/s1600/guy.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="618" data-original-width="374" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSCkxz1jY9AHeFawLaxcNYIDAQcTSZL_AvCXKiKzU_j4FGufYcp16_lN6pumVidJWYgXI9LDZF-K8IN1ksXtn58Km2ROx418PjobW7LOVp14slldxPLcWC33ubo6-tWYN87skEBuKCQp8/s320/guy.gif" width="193" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<h2>
修正方針</h2>
</div>
<div>
<br /></div>
<div>
今回は次の2つのアニメーションをそれぞれ別の方法で実装してみます。</div>
<div>
<br /></div>
<div>
<ol>
<li>平面検知された時に表示される飛行機をフェードインさせる</li>
<li>飛行機をきりもみ回転させる</li>
</ol>
<div>
<br /></div>
</div>
<div>
<br /></div>
<div>
<h3>
飛行機のフェードイン</h3>
</div>
<div>
<br /></div>
<div>
SCNTransactionと、物体の透過を設定するopacityというプロパティを使って実装します。<br />
<br />
まず<a href="https://www.nsunrise.work/2019/03/arkit_3.html">ARKitサンプルの飛行機を平面に置いてみよう</a>でshipの初期状態をHiddenにしましたが、これをopacityに変更しておきます。<br />
<br />
<pre class="prettyprint swift">
ship.opacity = 0
</pre>
このopacityは0〜1が設定でき、0が完全に透明な状態を示します。</div>
<div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; font-stretch: normal; line-height: normal;">
<span style="color: #1c00cf;"><br /></span></div>
<div style="font-stretch: normal; line-height: normal;">
<span style="background-color: white;">次に、</span>func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor)で飛行機を置いていますが、この処理の最後に次のSCNTransaction処理を追加します。</div>
<pre class="prettyprint swift">
SCNTransaction.begin()
SCNTransaction.animationDuration = 1.5
ship.opacity = 1
SCNTransaction.commit()
</pre>
<br />
<br />
<h3>
飛行機のきりもみ回転</h3>
</div>
<div>
<br /></div>
<div>
単純に飛行機を回転させる場合は次の2行で実装できます。<br />
<br />
<pre class="prettyprint swift">
let rotationAction = SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 0, z: 1, duration: 1))
ship.runAction(rotationAction)
</pre>
<br />
しかし、これを単純にshipに追加してしまうと、フェードインしている状態で回転をしてしまいます。<br />
<br />
フェードインが完了してから回転をしたい場合は次のように実装をします。<br />
<br />
<pre class="prettyprint swift">
let completion = {
let rotationAction = SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 0, z: 1, duration: 1))
ship.runAction(rotationAction)
}
SCNTransaction.begin()
SCNTransaction.animationDuration = 1.5
SCNTransaction.completionBlock = completion
ship.opacity = 1
SCNTransaction.commit()
</pre>
<br />
<h2>
実際に動作している様子</h2>
<div>
<br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwXdz9wVrKmMl31ROEN7s8wJqJLPA44rpCYdew0saTE2Z1Lh_qpd-bYynDLXBsyvXC9wFF83Fp0W3JfdWULD3UH8YfEHeohRcVdrK8C5hMeHPH-cNuD8xsTh3rTB_4FhUM4HMQnwl8fH0/s1600/Mar-07-2019+23-42-22.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="468" data-original-width="382" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwXdz9wVrKmMl31ROEN7s8wJqJLPA44rpCYdew0saTE2Z1Lh_qpd-bYynDLXBsyvXC9wFF83Fp0W3JfdWULD3UH8YfEHeohRcVdrK8C5hMeHPH-cNuD8xsTh3rTB_4FhUM4HMQnwl8fH0/s320/Mar-07-2019+23-42-22.gif" width="261" /></a></div>
<br /></div>
<div>
<br /></div>
<h2>
実際のコード</h2>
<pre class="prettyprint lang-swift">import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// Create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
if let ship = scene.rootNode.childNode(withName: "ship", recursively: true) {
ship.opacity = 0
}
// Setup Omni Light
let light = SCNLight()
light.type = .omni
let LightNode = SCNNode()
LightNode.light = light
LightNode.name = "light"
LightNode.position = SCNVector3(-1,2,-1)
scene.rootNode.addChildNode(LightNode)
// Set the scene to the view
sceneView.scene = scene
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = [.horizontal]
// Run the view's session
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
// MARK: - ARSCNViewDelegate
/*
// Override to create and configure nodes for anchors added to the view's session.
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
let node = SCNNode()
return node
}
*/
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
if let ship = sceneView.scene.rootNode.childNode(withName: "ship", recursively: true) {
ship.removeFromParentNode()
node.addChildNode(ship)
let configuration = ARWorldTrackingConfiguration()
sceneView.session.pause()
sceneView.session.run(configuration)
let completion = {
let rotationAction = SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 0, z: 1, duration: 1))
ship.runAction(rotationAction)
}
SCNTransaction.begin()
SCNTransaction.animationDuration = 1.5
SCNTransaction.completionBlock = completion
ship.opacity = 1
SCNTransaction.commit()
}
}
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
guard let lightEstimate = self.sceneView.session.currentFrame?.lightEstimate else { return }
let ambientIntensity = lightEstimate.ambientIntensity
let ambientColorTemperature = lightEstimate.ambientColorTemperature
if let lightNode = self.sceneView.scene.rootNode.childNode(withName: "light", recursively: true) {
guard let light = lightNode.light else { return }
light.intensity = ambientIntensity
light.temperature = ambientColorTemperature
}
}
func session(_ session: ARSession, didFailWithError error: Error) {
// Present an error message to the user
}
func sessionWasInterrupted(_ session: ARSession) {
// Inform the user that the session has been interrupted, for example, by presenting an overlay
}
func sessionInterruptionEnded(_ session: ARSession) {
// Reset tracking and/or remove existing anchors if consistent tracking is required
}
}
</pre>
</div>
<div>
<br /></div>
<div>
<br /></div>すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-57614781236935741212019-03-05T11:58:00.001+09:002019-03-20T16:10:24.036+09:00ARKitサンプルにライトを追加しよう<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrG0MupvmHp9vXXiEA5DFdaB-9pJKLSOrvYttuBUq1kH6r29A2xhA48Bg6FHoJKvscEWUw21LeOrlmK8kRD7bu8YD2jk3cuQWU4_Cku9HU3mklqjVzMJlX9neKdvU6gVIWQY2pNWhwCJo/s1600/06b5955b3585c9bc25b3a30ee32f841b_s.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="407" data-original-width="640" height="406" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrG0MupvmHp9vXXiEA5DFdaB-9pJKLSOrvYttuBUq1kH6r29A2xhA48Bg6FHoJKvscEWUw21LeOrlmK8kRD7bu8YD2jk3cuQWU4_Cku9HU3mklqjVzMJlX9neKdvU6gVIWQY2pNWhwCJo/s640/06b5955b3585c9bc25b3a30ee32f841b_s.jpg" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h2>
ARKitサンプルにライトを追加しよう</h2>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
標準のARKitサンプルで表示される飛行機には影が無く、のっぺりとした印象でリアリティさが欠けてしまっています。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
AR空間にライトを追加すると飛行機の凹凸に合わせて影ができて、よりリアルになります。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
また、ARKitの特徴としてカメラのビデオキャプチャより周囲の現実世界の明るさを取得してAR空間のライトの強弱を調整することができます。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEir-2cOPlWj5XfrLa1SrDGALYAlmyWnCsSbUzmRdbYSfMM0miW0pJ6GMdHFdqV08zOLK2NqculIWeY7SAWhMSd0gCkX_mrqtaefmEgB4uOFF63bfau_ULzkbA5Cavha42ralINqN3JYtzg/s1600/lighting-in-arkit.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="675" data-original-width="1150" height="233" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEir-2cOPlWj5XfrLa1SrDGALYAlmyWnCsSbUzmRdbYSfMM0miW0pJ6GMdHFdqV08zOLK2NqculIWeY7SAWhMSd0gCkX_mrqtaefmEgB4uOFF63bfau_ULzkbA5Cavha42ralINqN3JYtzg/s400/lighting-in-arkit.png" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
今回はそのライトの追加方法を紹介します。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h2>
修正方針</h2>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<ol>
<li>ライトをSceneに追加する</li>
<li>現在のビデオキャプチャ情報を取得するハンドラを追加する</li>
<li>明るさ情報をライトに反映させる</li>
</ol>
<br />
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h3>
ライトをSceneに追加する</h3>
<div>
<br /></div>
<div>
viewDidLoad()に次のコードを追加します。</div>
<pre class="prettyprint swift">
// Setup Omni Light
let light = SCNLight()
light.type = .omni
let LightNode = SCNNode()
LightNode.light = light
LightNode.name = "light"
LightNode.position = SCNVector3(-1,2,-1)
scene.rootNode.addChildNode(LightNode)
</pre>
<div>
<br /></div>
<div>
ライトの種類は.typeで指定をします。</div>
<div>
<br /></div>
<div>
次のように色々と種類がありますが、特にこだわりが無ければ電球の光を模したOmniライトが良いと思います。</div>
<div>
<br /></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjESu12Ci1qoj310WykhIQ7EhoaOvsdVxnAyilrIxiV0ctwOGjiJIiJ5A3989dE9wcDMGqDoaixGzxuXTByMpWQkxWFDEW20GD1O_bH33YA8cAR3LO_7F_7-P7mgXBh8Q0C2ycK7BmnPN4/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-05+11.18.59.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="502" data-original-width="500" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjESu12Ci1qoj310WykhIQ7EhoaOvsdVxnAyilrIxiV0ctwOGjiJIiJ5A3989dE9wcDMGqDoaixGzxuXTByMpWQkxWFDEW20GD1O_bH33YA8cAR3LO_7F_7-P7mgXBh8Q0C2ycK7BmnPN4/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-05+11.18.59.png" width="397" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
この状態で動作させると飛行機に陰影がついてリアルになります。<br />
<br /></div>
<div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFuGzly6vQYCh93KeVaU-RlBu94FXqhVMLNkrOOVpEILPWoTNt4732rCNp0en92GO_kLIsHmPlaJPOHvHM8acNBltB1RaFLDJgKBhkrJ71IA3CPsHFohI2Nt8bLNd-jiGTg5R6Hte-GCw/s1600/before-after.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1334" data-original-width="1600" height="332" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFuGzly6vQYCh93KeVaU-RlBu94FXqhVMLNkrOOVpEILPWoTNt4732rCNp0en92GO_kLIsHmPlaJPOHvHM8acNBltB1RaFLDJgKBhkrJ71IA3CPsHFohI2Nt8bLNd-jiGTg5R6Hte-GCw/s400/before-after.PNG" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
</div>
<div>
<br /></div>
<h3>
現在のビデオキャプチャ情報を取得するハンドラを追加する</h3>
<div>
<br /></div>
<div>
ビデオキャプチャ情報は現在のARFrameから取得することができます。</div>
<div>
<br /></div>
<div>
そして、ARFrameはARSessionから取得することができ、ARSessionが動いている状態では絶えず更新がされます。</div>
<div>
<br /></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDMGfUelMCQB28I8fzc-tojK1UEwlagjw5NgWL7UAlLmqhOI5p179RNDxjGQHZU4Yc1_3ElqbIboIIQUHK02xp5yC0S97SuVbscNFLSu-bU4ijbdAY3GfCK2c6U_OVvLhOzidFq_bVVaM/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-05+10.28.39.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="492" data-original-width="1600" height="195" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDMGfUelMCQB28I8fzc-tojK1UEwlagjw5NgWL7UAlLmqhOI5p179RNDxjGQHZU4Yc1_3ElqbIboIIQUHK02xp5yC0S97SuVbscNFLSu-bU4ijbdAY3GfCK2c6U_OVvLhOzidFq_bVVaM/s640/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-05+10.28.39.png" width="640" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
ではいつARSessionを参照すれば良いのかという疑問が湧いてきますが、それは下記のハンドラが実行された時に取得するのがセオリーです。</div>
<pre class="prettyprint swift">
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval){
}
</pre>
<div>
<br /></div>
<div>
上記のメソッドはARSCNViewDelegateのデリゲートメソッドであり、AR空間に何か変化がおきた時に呼ばれます。</div>
<div>
<br /></div>
<div>
当然、光量が変化した時にも呼ばれるためこのタイミングで処理をするのが一番です。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
単純に上記コードを追加すればOKです。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h3>
明るさ情報をライトに反映させる</h3>
<div>
<br /></div>
<div>
現在の明るさ情報はcurrentFrameのlightEstimate プロパティより取得することができます。</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
lightEstimateには次の2つのプロパティがあり、それぞれ先程追加したlightに設定します。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<ul>
<li>ambientIntensity → 光量</li>
<li>ambientColorTemperature → 光色</li>
</ul>
<br />
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
具体的には次のように設定をします。</div>
<pre class="prettyprint swift">
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
guard let lightEstimate = self.sceneView.session.currentFrame?.lightEstimate else { return }
let ambientIntensity = lightEstimate.ambientIntensity
let ambientColorTemperature = lightEstimate.ambientColorTemperature
if let lightNode = self.sceneView.scene.rootNode.childNode(withName: "light", recursively: true) {
guard let light = lightNode.light else { return }
light.intensity = ambientIntensity
light.temperature = ambientColorTemperature
}
}
</pre>
<h2>
変更後のソースコード</h2>
<div>
<br /></div>
<pre class="prettyprint lang-swift">import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// Create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
if let ship = scene.rootNode.childNode(withName: "ship", recursively: true) {
ship.isHidden = true
}
// Setup Omni Light
let light = SCNLight()
light.type = .omni
let LightNode = SCNNode()
LightNode.light = light
LightNode.name = "light"
LightNode.position = SCNVector3(-1,2,-1)
scene.rootNode.addChildNode(LightNode)
// Set the scene to the view
sceneView.scene = scene
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = [.horizontal]
// Run the view's session
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
// MARK: - ARSCNViewDelegate
/*
// Override to create and configure nodes for anchors added to the view's session.
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
let node = SCNNode()
return node
}
*/
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
if let ship = sceneView.scene.rootNode.childNode(withName: "ship", recursively: true) {
ship.removeFromParentNode()
node.addChildNode(ship)
ship.isHidden = false
let configuration = ARWorldTrackingConfiguration()
sceneView.session.pause()
sceneView.session.run(configuration)
}
}
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
guard let lightEstimate = self.sceneView.session.currentFrame?.lightEstimate else { return }
let ambientIntensity = lightEstimate.ambientIntensity
let ambientColorTemperature = lightEstimate.ambientColorTemperature
if let lightNode = self.sceneView.scene.rootNode.childNode(withName: "light", recursively: true) {
guard let light = lightNode.light else { return }
light.intensity = ambientIntensity
light.temperature = ambientColorTemperature
}
}
func session(_ session: ARSession, didFailWithError error: Error) {
// Present an error message to the user
}
func sessionWasInterrupted(_ session: ARSession) {
// Inform the user that the session has been interrupted, for example, by presenting an overlay
}
func sessionInterruptionEnded(_ session: ARSession) {
// Reset tracking and/or remove existing anchors if consistent tracking is required
}
}</pre>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-18741388458230626662019-03-03T12:06:00.000+09:002019-03-20T16:03:36.582+09:00ARKitサンプルの飛行機を平面に置いてみよう<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6tk24ZZ9MAwnsKHsBCUEdfs9RGucwUQjzckrGt6iOV0kGy5NjeAEhwUzfw-tMGxfuABMx4PAIltDBemzNoPwoOdZF2azagun85HY-8wKj8YFd9K7njunklEGdOOrYYsIzqHyUTiJRwk0/s1600/576a4c3f85bdcf02baea13b86c87c96d_s.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="427" data-original-width="640" height="426" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6tk24ZZ9MAwnsKHsBCUEdfs9RGucwUQjzckrGt6iOV0kGy5NjeAEhwUzfw-tMGxfuABMx4PAIltDBemzNoPwoOdZF2azagun85HY-8wKj8YFd9K7njunklEGdOOrYYsIzqHyUTiJRwk0/s640/576a4c3f85bdcf02baea13b86c87c96d_s.jpg" width="640" /></a></div>
<br />
<br />
<h2>
はじめに</h2>
<br />
ARKitはカメラを通して見える現実世界に仮想の3D空間を重ね合わせるため、座標という概念を理解する必要があります。<br />
<br />
座標の概念を図示したものが下記の図です。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUvHj9u1HqOdqXcZh0nPEoFbwBkWFxQ4_xClY80FN-yc3Ulbil6aBr0fxqyQemxMU4jFD0FK6BgmgY5JIybLgfRtl4AntyTb53rycXdrpUKfQaGgR9Tlnf-4DLF_NC4_dqaOosfjERybw/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-03+7.34.38.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="851" data-original-width="1331" height="255" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUvHj9u1HqOdqXcZh0nPEoFbwBkWFxQ4_xClY80FN-yc3Ulbil6aBr0fxqyQemxMU4jFD0FK6BgmgY5JIybLgfRtl4AntyTb53rycXdrpUKfQaGgR9Tlnf-4DLF_NC4_dqaOosfjERybw/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-03+7.34.38.png" width="400" /></a></div>
<br />
<br />
<br />
<h3>
ワールド座標</h3>
<div>
<br /></div>
ARKitが空有間を認識してセッションが開始されたときに決まるAR空間の原点座標。<br />
<div>
<br /></div>
<div>
原点座標であるため必ず (x y z) 共に (0 0 0) となります。</div>
<div>
<br /></div>
<div>
アンカー座標とカメラ座標はこの原点からいくつ離れているかという形で (x y z) の値が決まります。<br />
<h3>
</h3>
<div>
<br /></div>
<div>
<br /></div>
<h3>
アンカー座標</h3>
<div>
<br /></div>
<div>
ARKitが現実世界の平面や特定の画像を認識した時に決まる、そのものの場所を示す座標。</div>
<div>
<div>
<br /></div>
<div>
ワールド座標とカメラ座標は必ず一つですが、アンカー座標は検知したものの分だけ存在します。</div>
<div>
<br />
<br /></div>
<h3>
カメラ座標</h3>
<div>
<br /></div>
<div>
ユーザー(iOSデバイス)の位置を示す座標。</div>
<div>
<br /></div>
<div>
ユーザーが移動することにより、(x y z) の値はそれに合わせて変わリます。</div>
<div>
<br /></div>
<div>
<br /></div>
<h2>
飛行機を検知した平面に置く</h2>
<br />
標準のサンプルアプリを動かした時に飛行機が画面に表示されましたが、これはワールド座標に飛行機を表示しています。<br />
<br />
なので少し後ろに動かないと飛行機が見えません、初めてアプリを動かしたときに「画面に何も表示されないじゃないか!」と思った方もいたかも知れません。(実際に<br />
私がそうでした)<br />
<br />
ARKitでは検知した平面に何かを置くことがはじめの一歩です。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYjAjzN9e38LjeZm3uXAwdziFMAKVEo2MSvVZNlBpNlAaSAupTjPVn7nGfb0NUgxKxpzCQJC1-RcaFXM1ACEFr-dHS4jndg8S6PBmJ870TaSFFuPe-JwOzLGKX4fEAEkbMka4GLJaBZd0/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-03+7.34.38-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="851" data-original-width="1331" height="255" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYjAjzN9e38LjeZm3uXAwdziFMAKVEo2MSvVZNlBpNlAaSAupTjPVn7nGfb0NUgxKxpzCQJC1-RcaFXM1ACEFr-dHS4jndg8S6PBmJ870TaSFFuPe-JwOzLGKX4fEAEkbMka4GLJaBZd0/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-03+7.34.38-2.png" width="400" /></a></div>
<br />
<br />
デフォルトのサンプルアプリではViewControllerクラスがARSCNViewDelegateを継承しています。<br />
<br />
ARSCNViewDelegateではARAnchorに対応する3Dオブジェクトを操作するためのメソッドが定義されており、これらのメソッドに独自の処理を加えることで各アプリでやりたいことを実現します。<br />
<br />
<br />
<h3>
修正方針</h3>
<div>
<br /></div>
<div>
サンプルアプリで検知した平面に飛行機を置くためには次のようなことを行う必要があります。</div>
<div>
<br /></div>
<div>
<ol>
<li>アプリ開始時にロードされた飛行機オブジェクトを非表示にする</li>
<li>平面検知を有効にする</li>
<li>飛行機オブジェクトをARAnchorの場所へ移動させる</li>
<li>飛行機オブジェクトを表示する</li>
</ol>
<div>
<br /></div>
</div>
<h4>
アプリ開始時にロードされた飛行機オブジェクトを非表示にする</h4>
<pre class="prettyprint swift">// Create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
</pre>
<div>
<br /></div>
<div>
飛行機オブジェクトのロードはviewDidLoad()内の上記箇所で行っています。</div>
<div>
<br /></div>
<div>
これは飛行機オブジェクトのデータだけをロードしているのではなく、飛行機オブジェクトデータを含むSCNSceneをロードしています。</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaTQ7ZW2PrlOBUG3Opu27dLRyIV5tSSje_qU8ugQKNhyphenhyphen9uDawoWO-PVNEKU8bUsgGhHSlCdxqixPCC9tgeugmzCNdG1ASU26gwjxpzd1gA3-q7oqezdPslaZQzn4WoIlyc_WJWRQ_sh4o/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-03+11.08.56.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="356" data-original-width="658" height="216" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaTQ7ZW2PrlOBUG3Opu27dLRyIV5tSSje_qU8ugQKNhyphenhyphen9uDawoWO-PVNEKU8bUsgGhHSlCdxqixPCC9tgeugmzCNdG1ASU26gwjxpzd1gA3-q7oqezdPslaZQzn4WoIlyc_WJWRQ_sh4o/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-03+11.08.56.png" width="400" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
飛行機データはSCNSceneの中にあるRootNodeの下にぶら下がっており、それを取得するには下記のような処理を行います。</div>
<div>
<br /></div>
<pre class="prettyprint swift">let ship = scene.rootNode.childNode(withName: "ship", recursively: true)
</pre>
<div>
<br /></div>
<div>
このchildNode()のwithNameにはSCNファイル内のノードの名前を指定します。</div>
<div>
<br /></div>
<div>
実際にXcode上でファイルを開くと分かりますが、Scene graphの構成の中にshipというノードがあり、これが名前となります。</div>
<div>
<br /></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjio3xTc8p2SnuiS9xInVOX0dVJ4qSTVfTZFIkSa3zoD1dFWOOMSrFBo4RwlnOVL-vhZ9zWxYnNxcUNDz6tnRsPWqXUOwWCdZs4W45r6QgXIceDiTXlvXhFXBiRpYCmQqJn1XE4kpb9Vw8/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-03+11.16.50.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="438" data-original-width="982" height="177" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjio3xTc8p2SnuiS9xInVOX0dVJ4qSTVfTZFIkSa3zoD1dFWOOMSrFBo4RwlnOVL-vhZ9zWxYnNxcUNDz6tnRsPWqXUOwWCdZs4W45r6QgXIceDiTXlvXhFXBiRpYCmQqJn1XE4kpb9Vw8/s400/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-03+11.16.50.png" width="400" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
shipという名前のノードを取得できたので、HiddenプロパティをONにして非表示に設定しておきます。</div>
<div>
<br /></div>
<pre class="prettyprint swift">if let ship = scene.rootNode.childNode(withName: "ship", recursively: true) {
ship.isHidden = true
}
</pre>
<h4>
平面検知を有効にする</h4>
<div>
<br /></div>
<div>
デフォルトの状態では平面検知が有効になっていないのでviewWillAppear()内のARWorldTrakingConfigurationにplaneDetectionをONにするように設定を行います。</div>
<pre class="prettyprint swift">// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = [.horizontal]
</pre>
<div>
<div style="background-color: white; color: #007400; font-family: Menlo; font-size: 14px; font-stretch: normal; line-height: normal;">
<br /></div>
</div>
<div>
<br /></div>
<h4>
飛行機オブジェクトをARAnchorの場所へ移動させる</h4>
<div>
<br /></div>
<div>
ARAnchorが追加されたタイミングの独自処理は</div>
<pre class="prettyprint swift">func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor)
</pre>
<div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; font-stretch: normal; line-height: normal;">
<br /></div>
</div>
<div>
<br /></div>
<div>
内に実装を追加します。</div>
<div>
<br /></div>
<div>
先程と同じようにshipノードを取得しますが、先程のscene変数はviewDidLoad()のローカル変数だったのでこのメソッドの中には存在しません。</div>
<div>
<br /></div>
<div>
そのためViewControllerのメンバ変数であるsceneViewから取得を行います。</div>
<div>
<br /></div>
<div>
また、取得後はワールド座標上のshipノードをアンカー座標上に移動させます。</div>
<pre class="prettyprint swift">if let ship = sceneView.scene.rootNode.childNode(withName: "ship", recursively: true) {
ship.removeFromParentNode()
node.addChildNode(ship)
}
</pre>
<div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; font-stretch: normal; line-height: normal;">
<br /></div>
</div>
<div>
removeFromeParentNode()は読んで字のごとく現在そのノードがぶら下がっている親ノードから自分を削除します。</div>
<div>
<br /></div>
<div>
そして、addChildNode()でshipノードをARアンカーノードの下に持っていきます。</div>
<div>
<br /></div>
<div>
nodeは一体何かと思われるかもしれませんが、これはメソッドの引数で指定されており、SCNKit内でのARAnchorの場所を示すノードとなります。</div>
<div>
<br /></div>
<div>
<br /></div>
<h4>
飛行機オブジェクトを表示する</h4>
<div>
<br /></div>
<div>
これは先程と逆にHiddenをOFFにするだけです。</div>
<pre class="prettyprint swift">ship.isHidden = false
</pre>
<div>
<br /></div>
<h4>
(平面検知をOFFにしておく)</h4>
<div>
<br /></div>
<div>
これは+αの処理ですが、ARSessionにより平面検知が動いている間は新しい平面が検知されたり、すでに検知した平面情報がアップデートされます。</div>
<div>
<br /></div>
<div>
表示した飛行機をとりあえずそのまま動かないようにするためには一度平面検知をOFFにしておきます。</div>
<pre class="prettyprint swift">let configuration = ARWorldTrackingConfiguration()
sceneView.session.pause()
sceneView.session.run(configuration)
</pre>
<div>
<br /></div>
<h2>
最終的な変更後のソースコード</h2>
<div>
<br /></div>
<div>
最終的な変更後のソースコードは次のようになります。</div>
<div>
<br /></div>
<pre class="prettyprint lang-swift">class ViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// Create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
if let ship = scene.rootNode.childNode(withName: "ship", recursively: true) {
ship.isHidden = true
}
// Set the scene to the view
sceneView.scene = scene
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = [.horizontal]
// Run the view's session
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
// MARK: - ARSCNViewDelegate
/*
// Override to create and configure nodes for anchors added to the view's session.
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
let node = SCNNode()
return node
}
*/
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
if let ship = sceneView.scene.rootNode.childNode(withName: "ship", recursively: true) {
ship.removeFromParentNode()
node.addChildNode(ship)
ship.isHidden = false
let configuration = ARWorldTrackingConfiguration()
sceneView.session.pause()
sceneView.session.run(configuration)
}
}
func renderer(_ renderer: SCNSceneRenderer, willUpdate node: SCNNode, for anchor: ARAnchor) {
}
func session(_ session: ARSession, didFailWithError error: Error) {
// Present an error message to the user
}
func sessionWasInterrupted(_ session: ARSession) {
// Inform the user that the session has been interrupted, for example, by presenting an overlay
}
func sessionInterruptionEnded(_ session: ARSession) {
// Reset tracking and/or remove existing anchors if consistent tracking is required
}
}
</pre>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<br />
<br />
<br /></div>
</div>
すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-29713929862785270822019-03-01T10:57:00.002+09:002019-03-16T00:42:06.151+09:00ARKitのサンプルを動かしてみよう<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlZZ9RV6QbRCIkbIr-A9K34cwjjkwnwdSmXMAXXV_eVBlQZybP5-SPw0kHCLbFvBPY3lcOR34jtvIYDqNmbdKPIPVicxg9I5YJWt0Z_SVGfG99HYkq4kO4bg0Ztkj-DiTkoX2sP3h9lTA/s1600/c4189d0b13268917c60835932a53ce38_m.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1200" data-original-width="1600" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlZZ9RV6QbRCIkbIr-A9K34cwjjkwnwdSmXMAXXV_eVBlQZybP5-SPw0kHCLbFvBPY3lcOR34jtvIYDqNmbdKPIPVicxg9I5YJWt0Z_SVGfG99HYkq4kO4bg0Ztkj-DiTkoX2sP3h9lTA/s640/c4189d0b13268917c60835932a53ce38_m.jpg" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<span id="goog_2023926854"></span><span id="goog_2023926855"></span><br />
<br />
<h2>
ARKitのサンプルを動かしてみよう</h2>
<br />
ARKitを勉強しようと思ったらまずはXcodeのサンプルアプリのビルドから始めましょう!<br />
<br />
今後はこのサンプルアプリに機能を追加する形でARKitの開発に必要なことを紹介していきたいと思います。<br />
<br />
<br />
<h2>
はじめに</h2>
<div>
<br /></div>
<div>
Xcodeがインストールされていない場合はApp Storeより最新のXcodeをインストールしてください。</div>
<div>
<br /></div>
<div>
またARKitの開発はARKitが動作する実機が必要になり、実機で動かすためにはXcodeにAppleアカウントを登録する必要があります。</div>
<div>
<br /></div>
<div>
登録していない場合は<a href="https://tech.pjin.jp/blog/2016/03/25/%E3%80%90xcode%E3%80%91%E8%AA%B0%E3%81%A7%E3%82%82%E3%82%8F%E3%81%8B%E3%82%8Biphone%E5%AE%9F%E6%A9%9F%E3%83%86%E3%82%B9%E3%83%88%E3%81%AE%E8%A8%AD%E5%AE%9A%E6%89%8B%E9%A0%86%E3%81%BE%E3%81%A8%E3%82%81/">こちら</a>を参考に設定を行ってください。</div>
<div>
<br /></div>
<div>
なお本記事の作成時の環境は</div>
<div>
<ul>
<li>macOS 10.14.1</li>
<li>Xcode 10.1 (10B61)</li>
</ul>
<div>
となります。</div>
</div>
<div>
<br /></div>
<div>
<br /></div>
<h2>
Xcodeでのプロジェクト作成</h2>
<div>
<br /></div>
<div>
まずはXcodeを起動します。</div>
<div>
<br /></div>
<div>
起動後、メニューより File > New > Project... を選択します。</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXmaIQeizbT8VDNZzO9aeyh-kUvgSmhSlbwZqhlcfFkeOo5KrbscZhJvEWXyYsIGGE-DU8Ua4KUaS-S0VBb1MwB8PYSUTGd4UpYgypvMxY2uy29Ny04YvS0IWj_Xm6UVvLsIVRXtCzJh8/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-01+10.09.18.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="579" data-original-width="666" height="278" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXmaIQeizbT8VDNZzO9aeyh-kUvgSmhSlbwZqhlcfFkeOo5KrbscZhJvEWXyYsIGGE-DU8Ua4KUaS-S0VBb1MwB8PYSUTGd4UpYgypvMxY2uy29Ny04YvS0IWj_Xm6UVvLsIVRXtCzJh8/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-01+10.09.18.png" width="320" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
次にテンプレートより「Augmented Reality App」を選択して、NEXT選択します。</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNQ8CxYcfQYnfIiYro5lIW2tKn_WYsB60t4ZT1025me_32VR48vttJpE8OXwbRVt2Y9YLO7fP8H0asO9tC7NRIA9AgJjugza4gJePzfPv4BwHisHtGYMi-csz-1m0e9dz4mx1ICI4bDvY/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-01+10.09.47.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="558" data-original-width="765" height="233" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNQ8CxYcfQYnfIiYro5lIW2tKn_WYsB60t4ZT1025me_32VR48vttJpE8OXwbRVt2Y9YLO7fP8H0asO9tC7NRIA9AgJjugza4gJePzfPv4BwHisHtGYMi-csz-1m0e9dz4mx1ICI4bDvY/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-01+10.09.47.png" width="320" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
作成するプロジェクトの名前など聞かれますので適当に埋めます。</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAgqcF4U1VlnbuKD5uhep8SxFDgkAweS7YGs_Di7TP2TQSI8gmahbq7gflIaK6a4kQy0sVND7p99IYCYxBB7prVcGpMGe0e_nsAsRvIFiQWtzg4rOf25-VyEW31llQmPdQnp4-nrv2WmY/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-01+10.10.44.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="542" data-original-width="746" height="232" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAgqcF4U1VlnbuKD5uhep8SxFDgkAweS7YGs_Di7TP2TQSI8gmahbq7gflIaK6a4kQy0sVND7p99IYCYxBB7prVcGpMGe0e_nsAsRvIFiQWtzg4rOf25-VyEW31llQmPdQnp4-nrv2WmY/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-01+10.10.44.png" width="320" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
以上でプロジェクトの作成は完了です。</div>
<div>
<br /></div>
<div>
<br /></div>
<h2>
実機で動かす</h2>
<div>
<br /></div>
<div>
作成したプロジェクトを早速実機で動かしてみましょう。</div>
<div>
<br /></div>
<div>
実機をケーブルでMacに接続したら、Xcodeの左上の三角マークのついたボタンを押します。</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKGw4Amt02smRI0MzLJGYv32996f7HSETsHNUqK-mNZjK2bo0taAyGPVKa-Ixe19xPg-YpXvQVi1Di8OAHyuijMt1MKKrotfDcsehX1kifb-OEtk-jJv9L5YPK3-3RJQLto_GNMBTRS3I/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-01+10.33.07.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="305" data-original-width="597" height="163" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKGw4Amt02smRI0MzLJGYv32996f7HSETsHNUqK-mNZjK2bo0taAyGPVKa-Ixe19xPg-YpXvQVi1Di8OAHyuijMt1MKKrotfDcsehX1kifb-OEtk-jJv9L5YPK3-3RJQLto_GNMBTRS3I/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-03-01+10.33.07.png" width="320" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
初めて実機でアプリを動かす場合は端末側で自分の開発者Apple IDを信頼する設定が必要になります。</div>
<div>
<br /></div>
<div>
端末の案内に従って信頼をしてください。</div>
<div>
<br /></div>
<div>
無事にアプリが起動すると、カメラの映像に飛行機が映った画面が表示されます。</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhieF0qF5TmrIhaIX1ORMnziZqjwZ8JBf6Nxrq0x-Dg5CiYLLRsQMwqndHZNfgYWZTX9Pt_eaFiE5xm6xrSDB4JX2m0Inya7ufIXStiMxovsnWzf6QU_X0IqyTQEhSjk3xPLOSE_O74ZcE/s1600/IMG_1583.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1334" data-original-width="750" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhieF0qF5TmrIhaIX1ORMnziZqjwZ8JBf6Nxrq0x-Dg5CiYLLRsQMwqndHZNfgYWZTX9Pt_eaFiE5xm6xrSDB4JX2m0Inya7ufIXStiMxovsnWzf6QU_X0IqyTQEhSjk3xPLOSE_O74ZcE/s320/IMG_1583.png" width="179" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<br />
<div>
<br /></div>
<div>
<br /></div>
すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-30826838824198339742019-02-26T23:33:00.002+09:002019-03-16T00:42:06.188+09:00ARKitの仕組み<div class="separator" style="clear: both; text-align: center;">
</div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgd5-pT4pk7zR_IhqakRekmYnt1ZowaljUt4vby9ihJtq0JjT7N-UwTpagK1rB1A-UB4jZ9dYWScIODIflT_WvNm4cF6n96L-vpA4cQDd6xEh_jgtTFW2XdvbQQlB7TxlX8OIyHV38lfXY/s1600/mechanism.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1200" data-original-width="1600" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgd5-pT4pk7zR_IhqakRekmYnt1ZowaljUt4vby9ihJtq0JjT7N-UwTpagK1rB1A-UB4jZ9dYWScIODIflT_WvNm4cF6n96L-vpA4cQDd6xEh_jgtTFW2XdvbQQlB7TxlX8OIyHV38lfXY/s640/mechanism.jpg" width="640" /></a></div>
<br /></div>
<div>
<br /></div>
<h2>
ARKitの仕組み</h2>
<div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiU-QPu_RDcNDDt8hdG12mkFGo2LEWnaA0qdqKUfGkLjTdufvRx_9qy7klnr9Yx3AzYAPjKTyAdAenb-RzW8WsqXUdr9UGTq5u8VSqXTRvj8PtlPCaqZ5Ok2hwwOMo8ZGKVC9N6qLnYcNY/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-26+8.58.18.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="531" data-original-width="1600" height="212" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiU-QPu_RDcNDDt8hdG12mkFGo2LEWnaA0qdqKUfGkLjTdufvRx_9qy7klnr9Yx3AzYAPjKTyAdAenb-RzW8WsqXUdr9UGTq5u8VSqXTRvj8PtlPCaqZ5Ok2hwwOMo8ZGKVC9N6qLnYcNY/s640/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-26+8.58.18.png" width="640" /></a></div>
<br />
<br />
上の絵はARKitとアプリケーションとの関係を示したものになります。<br />
<br />
それぞれ何かを説明すると<br />
<br />
<ul>
<li>Application: あなたが作るアプリケーション</li>
<li>ARKit: ARを実現するためのフレームワーク</li>
<li>SceneKit: 3Dデータを扱うためのフレームワーク</li>
<li>SpriteKit: 2Dデータを扱うためのフレームワーク</li>
<li>Metal: コンピュータグラフィックスを扱うためのフレームワーク</li>
</ul>
<div>
<br /></div>
<div>
ARKitを利用すると、ApplicationはARKitより検知された画面上の平面情報等を受け取ることができます。</div>
<div>
<br /></div>
<div>
Applicationは受け取った情報(例えば平面の座標情報)を元にSceneKitかSpriteKit、Metalのいずれかの方法で画面上に独自の演出を行うことができます。</div>
<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBuiaD38PN-8u5L2xSCnjT3U64d0h2cOyidwIOhYoqVh92hlapAagyrRHIsl7LVhLg6FO3Ie_eGsadg2k0QIMAcwBw09fSwCYYBg4uUg-dt5E8rqHRPhL1YBmOnZcHPRdkcrAJPvs1KOE/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-26+8.58.31.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="580" data-original-width="1075" height="344" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBuiaD38PN-8u5L2xSCnjT3U64d0h2cOyidwIOhYoqVh92hlapAagyrRHIsl7LVhLg6FO3Ie_eGsadg2k0QIMAcwBw09fSwCYYBg4uUg-dt5E8rqHRPhL1YBmOnZcHPRdkcrAJPvs1KOE/s640/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-26+8.58.31.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<br />
ARKit自身はAVFoundationとCoreMotionというフレームワークからビデオフレーム情報とデバイスセンサー情報を受け取ります。<br />
<br />
単純なデバイスの向きだけであればジャイロセンサー情報だけで良いのですが、デバイスがどれだけ動いたかについては、加速度センサーを元に推定を行います。<br />
<br />
ARKitは現実世界に仮想情報を組み合わせるために、まず目の前の空間を認識する必要があります。<br />
<br />
最近流行りのデュアルカメラであれば奥行きを認識することが容易であるため空間を認識することも容易です。<br />
<br />
ARKitはデュアルカメラではない端末でも動作することができます。それは、複数の地点のビデオフレームと画像認識技術を駆使しているためです。<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjijJe-7FvgCGMhEvmPQG9ok7QrsEXCuQ4zCobCVWlZBs8pH-Spv3PO6XurTtDZvxBgg0oYSZ1_RPf-A060qKAuAc8ykmz-Bb9g8wOboyN3Ipn4dMFwl_7GbsCmHC-71D61Dut3OpFPRQE/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-26+23.16.23.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="617" data-original-width="1199" height="328" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjijJe-7FvgCGMhEvmPQG9ok7QrsEXCuQ4zCobCVWlZBs8pH-Spv3PO6XurTtDZvxBgg0oYSZ1_RPf-A060qKAuAc8ykmz-Bb9g8wOboyN3Ipn4dMFwl_7GbsCmHC-71D61Dut3OpFPRQE/s640/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-26+23.16.23.png" width="640" /></a></div>
<br />
<br /></div>
<div>
ARKitはビデオフレームから得た画像より特徴点をいくつか設定します。<br />
<br />
デバイスが別の地点に移動した時に先ほどの特徴点を見つけ出し、三角測量の要領で特徴点との距離を導き出します。<br />
<br />
その様にすることで空間を認識していきます。Apple公式のメジャーアプリでも開始時に端末を動かす様に求められますが、これは上記の様な動作をすることで空間を認識しているためです。<br />
<br />
<br /></div>
すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-56196526409028926422019-02-25T01:41:00.001+09:002019-03-16T00:42:06.370+09:00ARKitとは<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEasJb_E5eUBrI6pkUrUVHGNgy10JzpmOheL5kTm7woUba0VdyUM4xUxtYG_FaTQXiFqn3zKhdy3Q3KbAJKIs2x8e58A2GsGLDueFbdAXKnU5xgGO5ecZcJSFsVV4Zmv93Y2vj2N_j2dA/s1600/ar.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1067" data-original-width="1600" height="426" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEasJb_E5eUBrI6pkUrUVHGNgy10JzpmOheL5kTm7woUba0VdyUM4xUxtYG_FaTQXiFqn3zKhdy3Q3KbAJKIs2x8e58A2GsGLDueFbdAXKnU5xgGO5ecZcJSFsVV4Zmv93Y2vj2N_j2dA/s640/ar.jpg" width="640" /></a></div>
<h2>
<br /></h2>
<div>
<br /></div>
<h2>
概要</h2>
<div>
<br /></div>
<div>
ARKitとはiOSデバイスで拡張現実(AR)を実現するためのフレームワークです。</div>
<div>
<br /></div>
<div>
iOSデバイスのカメラとデバイスのモーションセンサから得られる情報を統合してARを実現します。</div>
<div>
<br /></div>
<div>
iOSデバイスには2つのフロントカメラとバックカメラがありますが、それぞれできることが異なります。</div>
<div>
<br /></div>
<div>
<ul>
<li>フロントカメラ: Faceトラッキングによるユーザーの顔情報を元にしたAR</li>
<li>バックカメラ: Worldトラッキングによるユーザーの周辺の空間情報を元にしたAR</li>
</ul>
<div>
<br /></div>
<div>
これまでもカメラの情報とモーションセンサの情報を取得する方法はありましたが、それらを組み合わせてARを実現するには複雑な計算が必要でした。<br />
<br /></div>
</div>
<div>
ARKitを利用することで開発者はユーザーに対してどの様なAR体験を提供するかということに専念できる様になりました。</div>
<div>
<br /></div>
<div>
<br /></div>
<h3>
具体的には何ができるのか?</h3>
<div>
<br /></div>
<div>
ARKitを利用すると次の様なAR体験を実現できる様になります。<br />
<br />
<br /></div>
<h4>
Faceトラッキングを利用した擬似スカウター</h4>
<div>
<br /></div>
<div>
<blockquote class="twitter-tweet" data-lang="ja">
<div dir="ltr" lang="ja">
ARスカウター(SceneKit & ARKit で制作)<a href="https://twitter.com/hashtag/%E3%83%89%E3%83%A9%E3%82%B4%E3%83%B3%E3%83%9C%E3%83%BC%E3%83%AB?src=hash&ref_src=twsrc%5Etfw">#ドラゴンボール</a><a href="https://t.co/HOoYJrODgr">pic.twitter.com/HOoYJrODgr</a></div>
— Future with AR / 拡張現実 (@xR_jp) <a href="https://twitter.com/xR_jp/status/1096644269660561409?ref_src=twsrc%5Etfw">2019年2月16日</a></blockquote>
<script async="" charset="utf-8" src="https://platform.twitter.com/widgets.js"></script>
</div>
<div>
<br />
<h4>
</h4>
<h4>
Worldトラッキングを利用したペットアプリ</h4>
<div>
<br />
<blockquote class="twitter-tweet" data-lang="ja">
<div dir="ltr" lang="en">
🐣 AI-driven AR pets will be huge. Think Tamagotchi times a gazillion 🐶 <a href="https://t.co/0BcdvIiFJ7">https://t.co/0BcdvIiFJ7</a> Hands down amazing work by <a href="https://twitter.com/ridgelinelabs?ref_src=twsrc%5Etfw">@ridgelinelabs</a> 💖 <a href="https://t.co/fuoOwuizqg">pic.twitter.com/fuoOwuizqg</a></div>
— Made With ARKit (@madewithARKit) <a href="https://twitter.com/madewithARKit/status/899379755505655810?ref_src=twsrc%5Etfw">2017年8月20日</a></blockquote>
<script async="" charset="utf-8" src="https://platform.twitter.com/widgets.js"></script>
</div>
<div>
<br /></div>
</div>
<h3>
</h3>
<h3>
ユーザーがARKitを利用するために必要な条件</h3>
<div>
<br />
<ol>
<li>A9以上のCPUを持つiOSデバイスであること</li>
<li>iOSのバージョンは11以上であること</li>
</ol>
<div>
<br /></div>
<div>
1の条件についてはiPhoneであればiPhone6S以上、iPadであれば第5世代以上となります。</div>
</div>
<div>
<br />
<br /></div>
<h3>
開発者がARKitで開発するために必要な条件</h3>
<div>
<br /></div>
<div>
<ol>
<li>Xcode 9.0 以上</li>
<li>macOS 10.12.6以上 </li>
</ol>
<div>
<br /></div>
<div>
2の条件を満たすためには、必然的にMacは下記のいずれかのものが必要になります。</div>
</div>
<div>
<br /></div>
<ul>
<li>iMac (Late 2009) 以降</li>
<li>MacBook (Late 2009) 以降</li>
<li>MacBook Pro (Mid 2010) 以降</li>
<li>MacBook Air (Late 2010) 以降</li>
<li>Mac mini (Mid 2010) 以降</li>
<li>Mac Pro (Mid 2010) 以降</li>
</ul>
<div>
<br /></div>
すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-52839598653771083792019-02-17T00:08:00.004+09:002019-03-16T23:22:55.769+09:00縦横それぞれで拡大縮小できるUIPinchGestureRecognizerの改良<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipqg1utuDtqqyct6Bd2zliJtQ686XN6sLUyF3hz_Yp2OXF4zkykFJxt9EzSE_w1GkPLz1nkq_yvNmtLHWBUP57AXRoEzBmduUotPzyBR6vQDtMfvTdMBGGt76ydLNjbGIWZA_LEKi0tUM/s1600/ios.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="214" data-original-width="325" height="210" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipqg1utuDtqqyct6Bd2zliJtQ686XN6sLUyF3hz_Yp2OXF4zkykFJxt9EzSE_w1GkPLz1nkq_yvNmtLHWBUP57AXRoEzBmduUotPzyBR6vQDtMfvTdMBGGt76ydLNjbGIWZA_LEKi0tUM/s320/ios.png" width="320" /></a></div>
<h2>
はじめに</h2>
<div>
<br /></div>
<div>
<a href="https://www.nsunrise.work/2019/02/uipinchgesturerecognizer.html">前回の投稿</a>で縦横それぞれで拡大縮小できるUIPinchGestureRecognizerを作ったが、次の様な問題点があった。</div>
<div>
<ul>
<li>たまにXとYのスケールが無限大になってしまう</li>
<li>拡大縮小→指を離す→拡大縮小を繰り返していると、急にXとYのスケールが変わってしまう</li>
</ul>
</div>
<h2>
</h2>
<h2>
</h2>
<h2>
原因</h2>
<div>
<br /></div>
<div>
調査してみたところそれぞれ次の様なことが原因であった。</div>
<div>
<br />
<br /></div>
<h3>
たまにXとYのスケールが無限大になってしまう</h3>
<div>
<br /></div>
<div>
これはtouchesBegan()が必ず、touchesMoved()よりも前に認識されるだろうという前提のコードになっており、XとYのスケールの計算時にinitPinchWidthとinitPinchHeightの初期値である0で値を割ってしまっていることが原因であった。touchesBegan()のタイミングでユーザが初めに置いた指の間隔からinitPinchWidthとinitPinchHeightを計算するため、touchesMoved()が先に呼ばれるとこの様な状態に陥ってしまう。</div>
<div>
<br /></div>
<div>
尚、<a href="https://developer.apple.com/documentation/uikit/uigesturerecognizer/1624234-delaystouchesbegan">Appleの公式のドキュメント</a>にも下記の様に記載があるが、</div>
<blockquote class="tr_bq">
When the value of this property is false (the default), views analyze touch events in <a href="https://developer.apple.com/documentation/uikit/uitouch/phase/began">UITouch.Phase.began</a> and <a href="https://developer.apple.com/documentation/uikit/uitouch/phase/moved">UITouch.Phase.moved</a> in parallel with the receiver. </blockquote>
<div>
<br /></div>
<div>
デフォルトの状態ではUIGestureRecognizerはbegan状態とmoved状態を並行して検知してしまうらしく、touchesBegan()がtouchesMoved()よりも先に呼ばれることを前提としてはいけなかった。<br />
<br /></div>
<div>
<br /></div>
<h3>
拡大縮小→指を離す→拡大縮小を繰り返していると、急にXとYのスケールが変わってしまう</h3>
<div>
<br />
これはオーバライドしたtouchesBegan()とtouchesMoved()の頭でスーパークラスのメソッドを呼んでしまっていることが原因であった。オーバライドしたメソッドの先頭で呼んでしまうとその時点でtouchイベントが発火して次のイベントが検知されてしまうことになる。そうなると、touchesMoved ()のなかでXとYの間隔を計算しているタイミングで次のtouchesBegan()イベントが発生してしまい、initPinchWidthとinitPinchHeightの値が書き換わり、スケールが狂ってしまっていた。<br />
<br /></div>
<h2>
修正コード</h2>
<div>
<br /></div>
<div>
上記2点の不具合を修正したコードは次の通り</div>
<div>
<br /></div>
<pre class="prettyprint lang-swift">class TwoDimentionsPinchGestureRecognizer : UIPinchGestureRecognizer {
private var initPinchWidth : CGFloat = 0
private var initPinchHeight : CGFloat = 0
private var _scaleX : CGFloat = 0
private var _scaleY : CGFloat = 0
var scaleX : CGFloat {
get { return _scaleX }
}
var scaleY : CGFloat {
get { return _scaleY }
}
override func touchesBegan(_ touches: Set<uitouch>, with event: UIEvent) {
guard touches.count == 2 else { return }
let locations = touches.compactMap { touch in
return touch.location(in: self.view)
}
initPinchWidth = abs(locations[0].x - locations[1].x)
initPinchHeight = abs(locations[0].y - locations[1].y)
super.touchesBegan(touches, with: event)
}
override func touchesMoved(_ touches: Set<uitouch>, with event: UIEvent) {
guard touches.count == 2 else { return }
guard initPinchWidth != 0 else { return }
guard initPinchHeight != 0 else { return }
let locations = touches.compactMap { touch in
return touch.location(in: self.view)
}
let newPinchWidth = abs(locations[0].x - locations[1].x)
let newPinchHeight = abs(locations[0].y - locations[1].y)
_scaleX = newPinchWidth / initPinchWidth
_scaleY = newPinchHeight / initPinchHeight
super.touchesMoved(touches, with: event)
}
override func touchesEnded(_ touches: Set<uitouch>, with event: UIEvent) {
initPinchWidth = 0
initPinchHeight = 0
super.touchesEnded(touches, with: event)
}
}
</uitouch></uitouch></uitouch></pre>
<div>
<br /></div>
すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-71076039813313808112019-02-14T00:21:00.000+09:002019-03-16T00:42:06.590+09:00縦横それぞれで拡大縮小できるUIPinchGestureRecognizer<h2>
はじめに</h2>
<div>
標準のUIPinchGestureRecognizerだと得られるScaleはタッチした2点の距離をベースに計算されます。そのため、縦に伸ばしたか横に伸ばしたかは結果に反映されません。</div>
<div>
縦横それぞれでScaleが欲しかったため、UIPinchGestureRecognizerを拡張してTwoDimentionsPinchGestureRecognizerというクラスを作成しました。</div>
<div>
<br /></div>
<h2>
実装と解説</h2>
<h3>
実際のコード</h3>
<div>
<pre class="prettyprint lang-swift">class TwoDimentionsPinchGestureRecognizer : UIPinchGestureRecognizer {
private var initPinchWidth : Float = 0
private var initPinchHeight : Float = 0
private var _scaleX : Float = 0
private var _scaleY : Float = 0
var scaleX : Float {
get { return _scaleX }
}
var scaleY : Float {
get { return _scaleY }
}
override func touchesBegan(_ touches: Set<uitouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
guard touches.count == 2 else { return }
let locations = touches.compactMap { touch in
return touch.location(in: self.view)
}
initPinchWidth = Float(abs(locations[0].x - locations[1].x))
initPinchHeight = Float(abs(locations[0].y - locations[1].y))
}
override func touchesMoved(_ touches: Set<uitouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
guard touches.count == 2 else { return }
let locations = touches.compactMap { touch in
return touch.location(in: self.view)
}
let newPinchWidth = Float(abs(locations[0].x - locations[1].x))
let newPinchHeight = Float(abs(locations[0].y - locations[1].y))
_scaleX = newPinchWidth / initPinchWidth
_scaleY = newPinchHeight / initPinchHeight
}
}</uitouch></uitouch></pre>
</div>
<div>
<h3>
解説</h3>
</div>
まず、touchesBeganで初回のタッチした2点間の縦横それぞれの距離を計算します。そして、touchesMovedで初回の縦横の距離からどれだけ変わったかの比率を計算して、それぞれscaleXとscaleYとして値を呼び出し元に渡します。<br />
<div>
尚、touchesBeganとtouchesMovedで取得されるtouchesはSetのため順番が保証されていません。より完成度を求めるならばcompactMapで得られた結果をソートする必要があります。</div>
<div>
<br /></div>
<div>
<br /></div>
<h2>
TwoDimentionsPinchGestureRecognizerのデモ</h2>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWo38-ZeKBIjCAIpXpQp8LrHGlTUdC9Mmi_PgpXHZXvb6RZ01byqybYROcfNFUGBeqYP-q_n8-i56m3f0YRgySITPJpbW9q-CdNmYANx27P6pZpzCJW5GT_EwDrobsqZn75xH2AGImp6g/s1600/demo.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="567" data-original-width="320" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWo38-ZeKBIjCAIpXpQp8LrHGlTUdC9Mmi_PgpXHZXvb6RZ01byqybYROcfNFUGBeqYP-q_n8-i56m3f0YRgySITPJpbW9q-CdNmYANx27P6pZpzCJW5GT_EwDrobsqZn75xH2AGImp6g/s320/demo.gif" width="180" /></a></div>
<div>
<br /></div>
<div>
</div>
すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-13326722409848290382019-02-10T00:36:00.000+09:002019-03-16T00:42:05.932+09:00UIKitの表示変化をメンバ変数に頼り過ぎてはいけない<h2>
はじめに</h2>
<div>
先日AR Cake Dividerをリリースしましたが、早速バグが見つかりました(汗)</div>
<div>
設定画面では設定項目をタップすると、ダイアログを表示し、そこに表示されるPickerで値を選択できる様にしています。そして、設定値は「ガイド形式」と「分割数」と二つ用意しているのですが、交互に表示していると急にアプリが落ちてしまうことがあります。</div>
<div>
このバグは私のとりあえずメンバ変数でいいやという安易な考えが招いており、戒めのためにも不具合の原因を説明させて頂きます。</div>
<div>
<br /></div>
<h2>
原因</h2>
<div>
Pickerが表示するリストはそれぞれ別の配列で定義しており、選ばれた設定項目とPickerで選択されたrowの値はViewControllerのメンバ変数として保持しています。</div>
<div>
交互にダイアログを操作されるとrowの値はその都度変わりますが、rowの値を元に配列の値を参照しようとしたときに、rowの値が既に書き換えられてしまっており、配列の範囲外の場所を参照してしまいエラーが発生していました。</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuxvP5cawFmwQY_RVmGSgQXODtSEuQDH8ps1WBtiycyjsh-yE9UneFiay4BD25Z7njecbeaxfWGxGiI3RrmfYE6VnvXEWsqzslpJ379ufVqu7Pjqupr8nfb37FRGKirSGpBpr8Z1A1r8M/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-09+23.12.01.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuxvP5cawFmwQY_RVmGSgQXODtSEuQDH8ps1WBtiycyjsh-yE9UneFiay4BD25Z7njecbeaxfWGxGiI3RrmfYE6VnvXEWsqzslpJ379ufVqu7Pjqupr8nfb37FRGKirSGpBpr8Z1A1r8M/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-09+23.12.01.png" width="183" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuxvP5cawFmwQY_RVmGSgQXODtSEuQDH8ps1WBtiycyjsh-yE9UneFiay4BD25Z7njecbeaxfWGxGiI3RrmfYE6VnvXEWsqzslpJ379ufVqu7Pjqupr8nfb37FRGKirSGpBpr8Z1A1r8M/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-09+23.12.01.png" imageanchor="1"></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3JLjhOAzJGzA_-ZYXqBctVOKemdZWwBRllC3fXuCbA2Npn-f9brUk9RqRV_lepoQNRB9nDiavphh4qyzCOiuztKjD87_5Bv0a4XTaCxYJ7PNy2rPwtwXgpGzlQHmCVaohuHHtrmZm0lE/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-09+23.12.14.png" imageanchor="1"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3JLjhOAzJGzA_-ZYXqBctVOKemdZWwBRllC3fXuCbA2Npn-f9brUk9RqRV_lepoQNRB9nDiavphh4qyzCOiuztKjD87_5Bv0a4XTaCxYJ7PNy2rPwtwXgpGzlQHmCVaohuHHtrmZm0lE/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-09+23.12.14.png" width="183" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOV-0gdM138B2HJiuGMHsyKRXwgA9ZABiYx8dyz0DRaFU56VdpU9PcvwjBNwYF5IgJTDgkz1CNbOEWrMy5bYWbqR5kh-A2paqnn-7VfY4lSwllse4kykNpPUeJkrWJh54EOT_5UW9CWX0/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-09+23.12.20.png" imageanchor="1"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOV-0gdM138B2HJiuGMHsyKRXwgA9ZABiYx8dyz0DRaFU56VdpU9PcvwjBNwYF5IgJTDgkz1CNbOEWrMy5bYWbqR5kh-A2paqnn-7VfY4lSwllse4kykNpPUeJkrWJh54EOT_5UW9CWX0/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-09+23.12.20.png" width="183" /></a></div>
<div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuxvP5cawFmwQY_RVmGSgQXODtSEuQDH8ps1WBtiycyjsh-yE9UneFiay4BD25Z7njecbeaxfWGxGiI3RrmfYE6VnvXEWsqzslpJ379ufVqu7Pjqupr8nfb37FRGKirSGpBpr8Z1A1r8M/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-09+23.12.01.png" imageanchor="1"><br /></a></div>
<div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuxvP5cawFmwQY_RVmGSgQXODtSEuQDH8ps1WBtiycyjsh-yE9UneFiay4BD25Z7njecbeaxfWGxGiI3RrmfYE6VnvXEWsqzslpJ379ufVqu7Pjqupr8nfb37FRGKirSGpBpr8Z1A1r8M/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-09+23.12.01.png" imageanchor="1"><br /></a></div>
<div>
例えば、上の様に2つのボタンでそれぞれ、別のPickerを表示させ、選択した値でラベルを更新するプログラムがあったとします。</div>
<div>
<br /></div>
<div>
<br /></div>
<pre class="prettyprint lang-swift">import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
@IBOutlet weak var label: UILabel!
enum ButtonId {
case one
case two
}
var selectedButton = ButtonId.one
var selectedRow = 0
let button1List : [String] = ["apple", "orange", "banana", "grape", "strawberry"]
let button2List : [String] = ["black", "white"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
@IBAction func pushButton1(_ sender: Any) {
selectedButton = .one
showAlert()
}
@IBAction func pushButton2(_ sender: Any) {
selectedButton = .two
showAlert()
}
func update() {
switch selectedButton {
case .one:
label.text = button1List[selectedRow]
case .two:
label.text = button2List[selectedRow]
default:
break
}
}
private func showAlert() {
let vc = UIViewController()
vc.preferredContentSize = CGSize(width: 250,height: 250)
let pickerView = UIPickerView(frame: CGRect(x: 0, y: 0, width: 250, height: 250))
pickerView.delegate = self
pickerView.dataSource = self
vc.view.addSubview(pickerView)
var title = ""
switch selectedButton {
case .one:
title = "fruit"
case .two:
title = "color"
default:
break
}
let editAlert = UIAlertController(title: title, message: "", preferredStyle: UIAlertController.Style.alert)
editAlert.setValue(vc, forKey: "contentViewController")
editAlert.addAction(UIAlertAction(title: "done", style: .default, handler: { (UIAlertAction) in
self.selectedRow = pickerView.selectedRow(inComponent: 0)
self.update()
}))
editAlert.addAction(UIAlertAction(title: "cancel", style: .cancel, handler: nil))
self.present(editAlert, animated: true)
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
switch selectedButton {
case .one:
return button1List.count
case .two:
return button2List.count
default:
return 0
}
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
switch selectedButton {
case .one:
return button1List[row]
case .two:
return button2List[row]
default:
return nil
}
}
}
</pre>
<div>
<br /></div>
<div>
<br /></div>
<div>
このソースの様に、メンバ変数のselectedRowでPickerの橋渡しを行い、別途update()を呼ぶ様にすると、update()内で配列を参照するタイミングでユーザ操作によりselectedRowの値が書き換えられるとアウトになります。<br />
<br />
この様な事を防ぐためにはCで言う"値渡し"でselectedRowの値をupdate()に伝える様にします。<br />
<br />
<pre class="prettyprint lang-swift"> func update(_ selectedRow : Int) {
switch selectedButton {
case .one:
label.text = button1List[selectedRow]
case .two:
label.text = button2List[selectedRow]
default:
break
}
}
private func showAlert() {
let vc = UIViewController()
vc.preferredContentSize = CGSize(width: 250,height: 250)
let pickerView = UIPickerView(frame: CGRect(x: 0, y: 0, width: 250, height: 250))
pickerView.delegate = self
pickerView.dataSource = self
vc.view.addSubview(pickerView)
var title = ""
switch selectedButton {
case .one:
title = "fruit"
case .two:
title = "color"
default:
break
}
let editAlert = UIAlertController(title: title, message: "", preferredStyle: UIAlertController.Style.alert)
editAlert.setValue(vc, forKey: "contentViewController")
editAlert.addAction(UIAlertAction(title: "done", style: .default, handler: { (UIAlertAction) in
let selectedRow = pickerView.selectedRow(inComponent: 0)
self.update(selectedRow)
}))
editAlert.addAction(UIAlertAction(title: "cancel", style: .cancel, handler: nil))
self.present(editAlert, animated: true)
}
</pre>
<br />
<br />
ついついクラス内でしか使わない値なのだからメンバ変数でいいやという発想になってしまいがちだったので、良い教訓になりました。<br />
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-12724287359409604662019-02-07T21:57:00.001+09:002019-03-16T00:42:05.860+09:00AR Cake Divider をリリースしましたこの度初めてのiOS向けアプリ <a href="https://itunes.apple.com/us/app/ar-cake-divider/id1450383292">AR Cake Divider</a> をリリースしました。<br />
ケーキやピザを等分するときに役に立つアプリです。これまで同じ様なアプリはありましたが、ARKitを使ってガイドを付けるアプリは世界初なはずです。<br />
<br />
<br />
<img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEilG7mRV24HJwqGcdnXHjqidFVscCieogMO6lAfr57MevfqaBCE41wenWNjmdlIBAewGKlM-47joCfPnpsgoBBu7syUDbmkRGukoiBUZGeSbH2af5v5wiolfcI08UtvmpJxsB9u7fSN6_c/s320/icon.png" width="320" /><span id="goog_1395395215"></span><span id="goog_1395395216"></span><a href="https://www.blogger.com/"></a><br />
<br />
予算の関係上スクリーンショットはピザになってしまいましたが…、今後ケーキを切ってるところをYouTubeにでもアップしたい思ってます。<br />
<br />
<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyHuAs-RuBW5jWQtAYGUBOCqia3hRch8FkFABxCnx2OZsQrR5ihF26z3FhFvgTZYEPLKvt4wKHg5THosrzCwnY5GXckY4bQCKk_1Eu0SdSbWX1SFd5PFDMe67BKEQWHAOWhabLxhfGN1I/s1600/IMG_1540.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyHuAs-RuBW5jWQtAYGUBOCqia3hRch8FkFABxCnx2OZsQrR5ihF26z3FhFvgTZYEPLKvt4wKHg5THosrzCwnY5GXckY4bQCKk_1Eu0SdSbWX1SFd5PFDMe67BKEQWHAOWhabLxhfGN1I/s320/IMG_1540.png" width="180" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyHuAs-RuBW5jWQtAYGUBOCqia3hRch8FkFABxCnx2OZsQrR5ihF26z3FhFvgTZYEPLKvt4wKHg5THosrzCwnY5GXckY4bQCKk_1Eu0SdSbWX1SFd5PFDMe67BKEQWHAOWhabLxhfGN1I/s1600/IMG_1540.png" imageanchor="1"></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxetCDYayu6-kF7VEhoKmTqh52afezOSYdsV_X3JjnV-65pIeNSI8KuMsGU6J7ZrnQZaWtacfXl-TkZqMM0MMRfmAfDY3oqmeTz4NTaBRSkLNSrlBW1i8dh7dZ0FdepB4pkEMJMnyFse0/s1600/IMG_1544.png" imageanchor="1"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxetCDYayu6-kF7VEhoKmTqh52afezOSYdsV_X3JjnV-65pIeNSI8KuMsGU6J7ZrnQZaWtacfXl-TkZqMM0MMRfmAfDY3oqmeTz4NTaBRSkLNSrlBW1i8dh7dZ0FdepB4pkEMJMnyFse0/s320/IMG_1544.png" width="180" /></a><br />
<br />
<br />すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-84349998025206617382019-02-05T23:30:00.002+09:002019-03-16T00:42:06.662+09:00ARアプリに必要なターゲットカーソルをBlenderで作る<h2>
はじめに</h2>
<div>
ARアプリを作ると、3Dオブジェクトをどこに置くかをユーザにわかりやすく伝えるためターゲットカーソルが必要になることがあります。</div>
<div>
Apple公式のメジャーアプリでも出てくるアレです。</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDTzuq8_IZalx95BMGwB8iFUy6v6yz8_qlm5iBy73QLCW_PK4OgNEMR_uyk6hCdpgk7zSAkYuiie1qzLqr8WhdXvqpRjNfaEwTHHGP7p1bO6gt8ma0-H1VdDmO41mDRMuBJRSyyyUyVjY/s1600/IMG_1546.png" imageanchor="1"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDTzuq8_IZalx95BMGwB8iFUy6v6yz8_qlm5iBy73QLCW_PK4OgNEMR_uyk6hCdpgk7zSAkYuiie1qzLqr8WhdXvqpRjNfaEwTHHGP7p1bO6gt8ma0-H1VdDmO41mDRMuBJRSyyyUyVjY/s320/IMG_1546.png" width="180" /></a></div>
<div>
<br />
このカーソルは切れ目が入っているためSCNTubeやSCNPlaneを組み合わせて作ることが難しいですが、Blenderを使えば簡単に作れるのでその方法を紹介します。</div>
<div>
<br /></div>
<h2>
作成環境</h2>
<div>
<ul>
<li>macOS 10.14.1</li>
<li>Blender 2.79</li>
</ul>
</div>
<h2>
作成手順</h2>
<h3>
1. Blenderを起動する</h3>
<div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiBfOlewuiaQ8x-WzS5cnZZU014OUhGAURUzXgmUM_NxZ46UJbUth4lwp0GgM1ZLQweewgdM7kZ4TICbOllPIBSNm0eWRmXCagGOiuiBCVzIsCAGSXPmL4BzSJR1AjeMqsboWQvT8dVBM/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.34.43.png" imageanchor="1"><img border="0" height="187" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiBfOlewuiaQ8x-WzS5cnZZU014OUhGAURUzXgmUM_NxZ46UJbUth4lwp0GgM1ZLQweewgdM7kZ4TICbOllPIBSNm0eWRmXCagGOiuiBCVzIsCAGSXPmL4BzSJR1AjeMqsboWQvT8dVBM/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.34.43.png" width="320" /></a></div>
<div>
Blenderを起動するとイニシャルのプロジェクトで上記のような立方体が表示されます。</div>
<div>
<br /></div>
<h3>
2. 立方体とカメラ、ライトを削除する</h3>
<div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeutA91J3XpWtcceYmeN4lXcKqy_Q7mnUdBq203HAoXqOJnxaBVAVGJZAFfW_SrZ2To7xzKghVLs4v_Oa0hp9xea-G0zqjncfsHU7DytUA9Whlp7HF01I6PD0AEub3flGTj9LlTN9CtSE/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.35.14.png" imageanchor="1"><img border="0" height="187" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeutA91J3XpWtcceYmeN4lXcKqy_Q7mnUdBq203HAoXqOJnxaBVAVGJZAFfW_SrZ2To7xzKghVLs4v_Oa0hp9xea-G0zqjncfsHU7DytUA9Whlp7HF01I6PD0AEub3flGTj9LlTN9CtSE/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.35.14.png" width="320" /></a></div>
<div>
邪魔なので右のペインから全て削除してしまいましょう。</div>
<div>
<br /></div>
<h3>
3. カーソルを原点に持ってくる</h3>
<h3>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTCrn_EoWK4DaG3Okf0cZRbDnSmifh186TjlRCljPJb9PNUANoNRVGdDH2fikKkmS18Wwb7Ki-LSRAb-j7GfqbDvx0mnauvDKvj33MGUrfBg55U36bji9uqQLcnm3C-p80YIoigj-0wCA/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.35.31.png" imageanchor="1"><img border="0" height="187" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTCrn_EoWK4DaG3Okf0cZRbDnSmifh186TjlRCljPJb9PNUANoNRVGdDH2fikKkmS18Wwb7Ki-LSRAb-j7GfqbDvx0mnauvDKvj33MGUrfBg55U36bji9uqQLcnm3C-p80YIoigj-0wCA/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.35.31.png" width="320" /></a></h3>
<div>
Shift + S でカーソルを原点に移動させることができます。</div>
<div>
<br /></div>
<h3>
4. 円メッシュを2つ作成する</h3>
<h3>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUlbeZgH0o9YkxxW5FI6S7EfpidLdsWIJN3osNmGqvfz4gVvp5njf-BtylLkQfnthLiXxbAivRc1amErA2ss8nz29wqdkHwW8GkaTVKUUXfCW0J5p4FKSIXS5oEsY6bGBUwiN3XEpiqq0/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.36.51.png" imageanchor="1"><img border="0" height="187" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUlbeZgH0o9YkxxW5FI6S7EfpidLdsWIJN3osNmGqvfz4gVvp5njf-BtylLkQfnthLiXxbAivRc1amErA2ss8nz29wqdkHwW8GkaTVKUUXfCW0J5p4FKSIXS5oEsY6bGBUwiN3XEpiqq0/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.36.51.png" width="320" /></a></h3>
<div>
左のペインの「作成」タブ→円を選択して円を作成します。</div>
<div>
そのまま「ツール」タブ→拡大縮小を選択すると円の大きさを調整できます。</div>
<div>
これを繰り返し大きい円と少し小さな円を2つ作成します。</div>
<div>
<br /></div>
<div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBRoGRuFThOXfs74G-1DxbXvOz6rPOh7I3DzqTR84UGhQU8u1-q1Wza1Xzx9Dm0Vnrh9ysKKCPT5RsMHuF3HVyTwcqCa8EtNZ6YTOXwSjl3odbAZoKlo827Qg2RReZvXgB_KeDBh87hMk/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.38.43.png" imageanchor="1"><img border="0" height="187" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBRoGRuFThOXfs74G-1DxbXvOz6rPOh7I3DzqTR84UGhQU8u1-q1Wza1Xzx9Dm0Vnrh9ysKKCPT5RsMHuF3HVyTwcqCa8EtNZ6YTOXwSjl3odbAZoKlo827Qg2RReZvXgB_KeDBh87hMk/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.38.43.png" width="320" /></a></div>
<div>
<br /></div>
<h3>
5. 2つの円メッシュを統合する</h3>
<div>
このままだとバラバラで面をはると穴なしの円になってしまうため2つを統合して細長いドーナッツを作ります。</div>
<div>
「ツール」タブ→統合を選択します。</div>
<div>
<br /></div>
<h3>
6. ドーナッツに面を張る</h3>
<div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiszBlN7es6yOmGWcQQ_t8SCWNlirI-S7h7sToF4Sn-rxD7N4AMgLn_cDjWzaii2KPVaSJG_30P0t5s0t-KykNorwZVchRLVDhAqh7wUxJP9F8UI7ZG4Ezgvt57ZX48AS6AJ8XwcVv7Vhc/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.46.29.png" imageanchor="1"><img border="0" height="187" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiszBlN7es6yOmGWcQQ_t8SCWNlirI-S7h7sToF4Sn-rxD7N4AMgLn_cDjWzaii2KPVaSJG_30P0t5s0t-KykNorwZVchRLVDhAqh7wUxJP9F8UI7ZG4Ezgvt57ZX48AS6AJ8XwcVv7Vhc/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.46.29.png" width="320" /></a></div>
<div>
<br /></div>
<div>
編集モードに変更して、下のペインのメニューから メッシュ > 面 > 面を張るを選択します。</div>
<div>
<br /></div>
<h3>
7. ドーナッツ(輪っか)を削る</h3>
<div>
このままではただの輪っかなのでターゲットカーソルらしく一部分を削ります。</div>
<div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihHidcL4SOS-oNnq2d1SzwiXKuoSNNfCsUMWaxTKHADTeSlOhS_pgkWML0uy2J6LreNe-h44PAkiDwwolBJecbGFHi0Vc9dHVgW5u0YoaMOoNp9OYncIUoimYiAwl5Aa90e5WIpCJ744E/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.48.28.png" imageanchor="1"><img border="0" height="187" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihHidcL4SOS-oNnq2d1SzwiXKuoSNNfCsUMWaxTKHADTeSlOhS_pgkWML0uy2J6LreNe-h44PAkiDwwolBJecbGFHi0Vc9dHVgW5u0YoaMOoNp9OYncIUoimYiAwl5Aa90e5WIpCJ744E/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.48.28.png" width="320" /></a></div>
<div>
編集モードのままCを押して削りたい部分の頂点を選択します。</div>
<div>
そのまま、メニューの削除→頂点を選択します。</div>
<div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYV3NEfXh1lfR9VrEwbBkyARDFcsjLjXzYGuw6A048UmICxVnkiKLuVzdZUIC7wNmyryW6TLrzywrXJ9dH40xpgOqPenBkTjsB8c3GSOIC-8AwguVEk_I2dQlIfDns2C4uzftpYWrKKxk/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.49.08.png" imageanchor="1"><img border="0" height="187" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYV3NEfXh1lfR9VrEwbBkyARDFcsjLjXzYGuw6A048UmICxVnkiKLuVzdZUIC7wNmyryW6TLrzywrXJ9dH40xpgOqPenBkTjsB8c3GSOIC-8AwguVEk_I2dQlIfDns2C4uzftpYWrKKxk/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.49.08.png" width="320" /></a></div>
<div>
同じ作業を繰り返して3箇所削ります。</div>
<div>
<br /></div>
<h3>
8. 中央に小さな円メッシュを張る</h3>
<div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHQCN7aP1-xyInQC5HDkvNhbJaHr6rkg9pyLbsD2T-U5zvF38FQ6ilgfAF4ojDom08w9UFy7dExlfJ2XZfAuK9j0wVouINcf6A21vsk8cKEoNezUI6cGW3fpoTOuRO50bxzNQwCGGbvwc/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.49.54.png" imageanchor="1"><img border="0" height="187" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHQCN7aP1-xyInQC5HDkvNhbJaHr6rkg9pyLbsD2T-U5zvF38FQ6ilgfAF4ojDom08w9UFy7dExlfJ2XZfAuK9j0wVouINcf6A21vsk8cKEoNezUI6cGW3fpoTOuRO50bxzNQwCGGbvwc/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.49.54.png" width="320" /></a></div>
<div>
中央にカーソルを持っていき先ほどの手順で小さな円メッシュを張ります。</div>
<div>
<br /></div>
<h3>
9. 両面表示設定をする</h3>
<div>
このままだと中央の円と周りの輪っかがそれぞれ裏表逆になることがあるので、裏表無しになるように右側のペインを操作して両面表示をONにします。</div>
<div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqZmQzk3o61TOjczo698B2GhmuAPpoObn9vR_ZoeDWG1FrD9soxOphhr5JoPQmfjYZFkLOYzbVKn6wjxs7r0BRIa3yKFJdIR9upUbnX9JaFggfFkxHVOrg-DXkJ-RClpdeqRVH4uHwaq4/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.55.13.png" imageanchor="1"><img border="0" height="187" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqZmQzk3o61TOjczo698B2GhmuAPpoObn9vR_ZoeDWG1FrD9soxOphhr5JoPQmfjYZFkLOYzbVKn6wjxs7r0BRIa3yKFJdIR9upUbnX9JaFggfFkxHVOrg-DXkJ-RClpdeqRVH4uHwaq4/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.55.13.png" width="320" /></a></div>
<div>
<br /></div>
<h3>
10. DAE形式のファイルでエクスポート</h3>
<div>
Xcodeで扱うためDAE(Collada)形式でエクスポートします。</div>
<div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgD6LGz16pCmdIhfXCnQKroKqQHJwlFY36KXUB-9hkjJ25EW7C8xvJkzOa9w6ZEZGwINPziGJcE45u1wsLJT8Ig0xxzis6Ye1heUm-BbECYfji7ytrqtdl-8LvqqR_amc3h-nchAewAvRI/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.56.03.png" imageanchor="1"><img border="0" height="187" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgD6LGz16pCmdIhfXCnQKroKqQHJwlFY36KXUB-9hkjJ25EW7C8xvJkzOa9w6ZEZGwINPziGJcE45u1wsLJT8Ig0xxzis6Ye1heUm-BbECYfji7ytrqtdl-8LvqqR_amc3h-nchAewAvRI/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.56.03.png" width="320" /></a></div>
<div>
<br /></div>
<div>
エクスポートが完了すると下記のようにXcodeで扱えるターゲットカーソルが出来上がります。</div>
<div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjLz5Kv5nBwxfYlqyPhBgGDJIczZUNKwwH5TzV8Od2lWaU0Sa1tK-l_4uB4ySIuFXBuwiY7IpPHJvIeQNr3EvCDoTiw7oNBLmmV5nYLsfXymiTduj8GZjrGbaQHkfLN0JekMl0ShE2Zto/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.56.32.png" imageanchor="1"><img border="0" height="285" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjLz5Kv5nBwxfYlqyPhBgGDJIczZUNKwwH5TzV8Od2lWaU0Sa1tK-l_4uB4ySIuFXBuwiY7IpPHJvIeQNr3EvCDoTiw7oNBLmmV5nYLsfXymiTduj8GZjrGbaQHkfLN0JekMl0ShE2Zto/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588+2019-02-05+22.56.32.png" width="320" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-14038045136340243222019-01-30T23:50:00.005+09:002019-03-16T00:42:05.968+09:00ARアプリ開発するのにもPlaygroundが便利<h2>
はじめに</h2>
<div>
今ARKitを使ったiOS向けのアプリの開発をしています。ARKitは3Dゲーム開発のためのフレームワークであるSCNKitの延長線にあるため、よく自分の書いたコードが3D空間で思い通りに動いているか確認をします。</div>
<div>
これまでは、都度実機で確認を行なっていたのですが、座標変換などの簡単な確認をするのにもいちいち実機で動かすのは面倒に思っていました。しかし、最近PlaygroundでもSCNKitやSpriteKitを動かせることを知り、これは便利だと思ったので紹介します。</div>
<div>
<br /></div>
<h2>
手順</h2>
<h3>
1. Xcodeのメニュー > File > New > Playground...を選択する</h3>
<h3>
2. Gameを選択する</h3>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXfEYg8bWzUsA584KGDKQqfuSVNgVEEdY29EkcITRRIjtdlx7tVTYHRCVJ7N-vyb4IpLoUjvOnUcHx-w_rsZJFnR7-ODpguDzZUXe5tw5LFR4eveEm1lOl7lQ7Nh7KKKfvhSXt_ej4wME/s1600/1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="751" data-original-width="1072" height="448" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXfEYg8bWzUsA584KGDKQqfuSVNgVEEdY29EkcITRRIjtdlx7tVTYHRCVJ7N-vyb4IpLoUjvOnUcHx-w_rsZJFnR7-ODpguDzZUXe5tw5LFR4eveEm1lOl7lQ7Nh7KKKfvhSXt_ej4wME/s640/1.png" width="640" /></a></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVesA8hwIfd4jwDD4zbcl0z_ds9rbLhVsGa09h0BCe3dYNZbBY_LtFJIlP8qh6c6CqW2JWMmlcZgNiiU8Wxl5IKudtJqpbwsqENMFts5QzS6QjK8DvDocQk4Zv264Dj0drR-_CJhdnuIs/s1600/2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="900" data-original-width="1186" height="484" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVesA8hwIfd4jwDD4zbcl0z_ds9rbLhVsGa09h0BCe3dYNZbBY_LtFJIlP8qh6c6CqW2JWMmlcZgNiiU8Wxl5IKudtJqpbwsqENMFts5QzS6QjK8DvDocQk4Zv264Dj0drR-_CJhdnuIs/s640/2.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
画面の左下のexecuteボタンを押すことでコードが実行できますが、この状態では何も表示されません。</div>
<div>
<br /></div>
<h3>
3. 右上の Show the Assistant Editor を押す</h3>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTV9AjRbBCM3CRLO2oaWcwGdVUYzM-UpfEga4Se47awyaok3paKdPREMn8UK6sHHTHKp1WvdKYgMLLUfRQEcozQJ09VePLmGNYdbsebH1G1O5jxsM76KGtFsc4a-I-5z9q5kWQaRJtH_w/s1600/3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="851" data-original-width="1600" height="339" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTV9AjRbBCM3CRLO2oaWcwGdVUYzM-UpfEga4Se47awyaok3paKdPREMn8UK6sHHTHKp1WvdKYgMLLUfRQEcozQJ09VePLmGNYdbsebH1G1O5jxsM76KGtFsc4a-I-5z9q5kWQaRJtH_w/s640/3.png" width="640" /></a></div>
<h3>
4. Assistant Editorを開いた状態でコードを実行する</h3>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhd9hhoH1ORn4L_V7Cg-YvLho-P_o54IpPYiE-vKXUvkSOD9YS_zNK5bvQAOUQPqTj4jS5gsbuOiuTjhxV0wuRMNNBJ4V6loNbpCxlQgPIMqaRrsVu-VcaCs5UBqxzvLQr8etURmmxOPBo/s1600/5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="851" data-original-width="1600" height="339" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhd9hhoH1ORn4L_V7Cg-YvLho-P_o54IpPYiE-vKXUvkSOD9YS_zNK5bvQAOUQPqTj4jS5gsbuOiuTjhxV0wuRMNNBJ4V6loNbpCxlQgPIMqaRrsVu-VcaCs5UBqxzvLQr8etURmmxOPBo/s640/5.png" width="640" /></a></div>
<div>
<br /></div>
<div>
Assistant Editor上に擬似画面が表示され、SpriteKitのコードが実行されます。</div>
<div>
<br /></div>
<div>
ちゃんとマウスカーソルで画面を押すことでインタラクションイベントも確認することができます。</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYAwFh6UT_NvVywcBJzQHTys109btU7liWUOE3fOOC6ZkgJ3p91tEuZPuqfAuIMsiV7REsh_EsUcUjV4AAMf8Aw9bH8Yn_hp_8LkjTAhbymaGs5KdjPfRM69an0rSVJBl4t0v6VXa8HPE/s1600/interaction.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="538" data-original-width="790" height="434" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYAwFh6UT_NvVywcBJzQHTys109btU7liWUOE3fOOC6ZkgJ3p91tEuZPuqfAuIMsiV7REsh_EsUcUjV4AAMf8Aw9bH8Yn_hp_8LkjTAhbymaGs5KdjPfRM69an0rSVJBl4t0v6VXa8HPE/s640/interaction.gif" width="640" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<h3>
</h3>
すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-66391419839695447712019-01-27T01:50:00.000+09:002019-03-16T22:42:42.428+09:00Android 7.0以降だとSAF経由でGoogleDriveへファイル作成ができないことがある<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<h2>
</h2>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmpSs9wgj0raD1mtwv90cyIIyNY_wlC4dDofGX6o7p3W_sD-71WQDIvqc2JWN4IMe2cv3kFgsKAZ8iIqIj3nBnBLLWNuAG2EuXCAyD7u3TgCEYjCwUl4_1gGRduxnT3N4D6otfyxCB1jw/s1600/_android.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="250" data-original-width="400" height="250" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmpSs9wgj0raD1mtwv90cyIIyNY_wlC4dDofGX6o7p3W_sD-71WQDIvqc2JWN4IMe2cv3kFgsKAZ8iIqIj3nBnBLLWNuAG2EuXCAyD7u3TgCEYjCwUl4_1gGRduxnT3N4D6otfyxCB1jw/s400/_android.png" width="400" /></a></div>
<h2>
はじめに</h2>
<div>
<br /></div>
<div>
最近Androidアプリのメンテナンスをしていなかったので一番の稼ぎ頭である<a href="https://play.google.com/store/apps/details?id=com.nsunrise.nudia">Nudia</a>の不具合修正を行うことにしたが、結構大変だったので内容を残しておく。</div>
<div>
<br /></div>
<h2>
不具合の内容と調査結果</h2>
<div>
<br /></div>
<h3>
不具合の内容</h3>
<div>
<br /></div>
<div>
アプリバージョン 2.3.0でAndroid 7.0以降の端末で作成した画像ファイルをGoogleDrive上に保存しようとすると0 byteの空ファイルができる。</div>
<div>
<br /></div>
<div>
調べてみると、<a href="https://stackoverflow.com/questions/51015513/fileoutputstream-writes-0-bytes-to-google-drive">stackoverflow</a>の方に同じ悩みを抱えている人がいたが原因と対策は分かっていない様だった。</div>
<div>
<br /></div>
<h3>
調査結果</h3>
<div>
<br /></div>
<div>
当初、Android Nougatからアプリ間のファイル共有のポリシーが厳しくなったことにより、プログラム側での処理が足りていないのかと思っていたが、ファイルの保存方法は<a href="https://developer.android.com/guide/topics/providers/document-provider?hl=ja">Android公式のSAF</a>に紹介されている手順に従っているため、他に原因があるはずだが全く見当が付かなかった。</div>
<div>
<br /></div>
<div>
そして施行錯誤をする中で、なぜかGoogleDriveを再起動した直後の1回だけ保存が成功することがわかった。</div>
<div>
<br /></div>
<div>
ちなみに公式のSAFのドキュメントでACTION_OPEN_DOCUMENTインテントとACTION_CREATE_DOCUMENTインテントを紹介してCATEGORY_OPENABLEと組み合わせることで次の3パターンのファイルwrite操作が可能になる。</div>
<div>
<br /></div>
<div>
<table cellpadding="0" cellspacing="0" style="border-collapse: collapse; color: black;"><tbody>
<tr><td style="background-color: #b0b3b2; border: 1px solid rgb(0, 0, 0); height: 12px; padding: 4px; width: 202px;" valign="top"><div style="font-family: Helvetica; font-size: 12px; font-stretch: normal; line-height: normal; min-height: 14px;">
<br /></div>
</td><td style="background-color: #b0b3b2; border: 1px solid rgb(0, 0, 0); height: 12px; padding: 4px; width: 180px;" valign="top"><span style="color: black; font-family: "hiragino sans cns"; font-size: 12px; font-stretch: normal; line-height: normal;">CATEGORY_OPENABLE あり</span></td><td style="background-color: #b0b3b2; border: 1px solid rgb(0, 0, 0); height: 12px; padding: 4px; width: 175px;" valign="top"><span style="color: black; font-family: "hiragino sans cns"; font-size: 12px; font-stretch: normal; line-height: normal;">CATEGORY_OPENABLE なし</span></td></tr>
<tr><td style="background-color: #d4d4d4; border: 1px solid rgb(0, 0, 0); height: 11px; padding: 4px; width: 202px;" valign="top"><span style="color: black; font-family: "hiragino sans cns"; font-size: 12px; font-stretch: normal; line-height: normal;">ACTION_CREATE_DOCUMENT</span></td><td style="border: 1px solid rgb(0, 0, 0); height: 11px; padding: 4px; width: 180px;" valign="top"><div align="center" style="text-align: center;">
<span style="color: black; font-family: "hiragino sans cns"; font-size: 12px; font-stretch: normal; line-height: normal;">新規にファイルを作成</span></div>
</td><td style="border: 1px solid rgb(0, 0, 0); height: 11px; padding: 4px; width: 175px;" valign="top"><div align="center" style="text-align: center;">
<span style="color: black; font-family: "hiragino sans cns"; font-size: 12px; font-stretch: normal; line-height: normal;">空ファイル作成</span></div>
</td></tr>
<tr><td style="background-color: #d4d4d4; border: 1px solid rgb(0, 0, 0); height: 11px; padding: 4px; width: 202px;" valign="top"><span style="color: black; font-family: "hiragino sans cns"; font-size: 12px; font-stretch: normal; line-height: normal;">ACTION_OPEN_DOCUMENT</span></td><td style="border: 1px solid rgb(0, 0, 0); height: 11px; padding: 4px; width: 180px;" valign="top"><div align="center" style="text-align: center;">
<span style="color: black; font-family: "hiragino sans cns"; font-size: 12px; font-stretch: normal; line-height: normal;">既存ファイルを上書き</span></div>
</td><td style="border: 1px solid rgb(0, 0, 0); height: 11px; padding: 4px; width: 175px;" valign="top"><div align="center" style="text-align: center;">
<span style="color: black; font-family: "hiragino sans cns"; font-size: 12px; font-stretch: normal; line-height: normal;">ー</span></div>
</td></tr>
</tbody></table>
</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
アプリ側ではACTION_CREATE_DOCUMENTとCATEGORY_OPENABLEを指定しているのだが、何故か2回目以降だとCATEGORY_OPENABLEが効いていなかった。</div>
<div>
またlogcatでGoogleDriveの動作を監視してみると、再起動直後のみアプリでの保存操作後ActivityManagerから何かしらのIntentがGoogleDriveへ飛んでいることが分かった。</div>
<div>
しかしこれ以上の原因については分からなかったため、暫定対応としてAndroid Nougat以上ではGoogleDriveへの保存をさせないように制限を付けることにした。</div>
<div>
<br /></div>
<div>
もし今後さらなる原因がわかったら、恒久的な修正をリリースします。</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
最後に、これまでアプリをご利用くださっていた方にはご不便をおかけして申し訳ありませんでした。</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-60799325421761266632019-01-26T01:02:00.001+09:002019-03-16T00:42:06.519+09:00GoogleDirveでのStackEdit解除方法<br />
<br />
<h2>
はじめに</h2>
<br />
AndroidからiOSアプリへ開発を切り替えたのをきっかけにこのブログではなくQiitaの方へ投稿をしていたのですが、プログラミング以外のことも書きたいのでこっちを再開しようかと考えています。<br />
<br />
Qiitaの投稿はMarkdownだったため、そちらで慣れているとBloggerでもMarkdownで書きたくなります。調べてみるとStackEditというWebツールを使えばMarkdown形式で書けるとのことで導入して見ましたが次の理由でやっぱりやめました。<br />
<br />
<h2>
StackEditについて気になった点</h2>
<br />
<ul>
<li>画像を投稿に含める場合、先にどこかにアップしてそのURLを指定しなければならない(ドラッグ&ドロップで自動でアップロードしてくれない)</li>
</ul>
<div>
<br /></div>
<ul>
<li>StackEditで作ったドキュメントだけとは言え、自分のGoogleDriveへのアクセスを付けるのが嫌</li>
</ul>
<div>
<br /></div>
<ul>
<li>そもそも、GoogleDrive上でStackEditドキュメントを作成しようとしたときに確認されるアカウント紐付けで、「Restrict access」を選択しないとGoogleDrive上の全てのアクセス権を要求されるのが物凄く嫌</li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgA28R03Zdzf4r63AePU_uY80hUHkZXAJjpNRrtd4vbmb0wZG1CrgD4iBVqn5obz1fAwMxl2hljooPyO5xtZINZt26uTlTtOklB8L6MRDE35Qovm56kozIyVbNx880eYuIKySGmqt3mUXs/s1600/A.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="220" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgA28R03Zdzf4r63AePU_uY80hUHkZXAJjpNRrtd4vbmb0wZG1CrgD4iBVqn5obz1fAwMxl2hljooPyO5xtZINZt26uTlTtOklB8L6MRDE35Qovm56kozIyVbNx880eYuIKySGmqt3mUXs/s320/A.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQfUF80T8Je2K96PFYzgPFfsYcYenY5V3hs9fvS0y6KV8s7dO5Ug3WRZI07kgahhjKB33HKNQe0KAs0BBFsb7uNlty6XyMW5cBqgxf-xmIM6Tw_Fv2sv8ULfqmrChJ1n2aYqSMxVva3KY/s1600/C.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQfUF80T8Je2K96PFYzgPFfsYcYenY5V3hs9fvS0y6KV8s7dO5Ug3WRZI07kgahhjKB33HKNQe0KAs0BBFsb7uNlty6XyMW5cBqgxf-xmIM6Tw_Fv2sv8ULfqmrChJ1n2aYqSMxVva3KY/s320/C.png" width="203" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
そのためGoogleDriveに設定したStackEditを解除することにしましたが、やり方が分からず戸惑ったのでやり方を残しておきます。</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h2>
StackEditの解除方法</h2>
<div class="separator" style="clear: both; text-align: left;">
</div>
<ul>
<li>GoogleDriveを開く</li>
</ul>
<div>
<br /></div>
<ul>
<li>右上の設定アイコンを選択し、設定を開く<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9h_8XDSioGSb6ApdvXas0rwwmqhuPsBVy4ZjIfP8DWUrqEW8WiI8lhJRxhMEqKD1big1lsLJKdnpa1P5V4Oh8Gp1CxUYch1za6bpKvmVCPPYw8Djvk1EfIV0zi9EjkAsokoP6oxvigAU/s1600/2.png" imageanchor="1"><img border="0" height="297" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9h_8XDSioGSb6ApdvXas0rwwmqhuPsBVy4ZjIfP8DWUrqEW8WiI8lhJRxhMEqKD1big1lsLJKdnpa1P5V4Oh8Gp1CxUYch1za6bpKvmVCPPYw8Djvk1EfIV0zi9EjkAsokoP6oxvigAU/s640/2.png" width="640" /></a></li>
</ul>
<div>
<br /></div>
<ul>
<li>左のペインよりアプリの管理を選択し、一覧の中にあるStackEditを選び「ドライブから切断」をクリックする</li>
</ul>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMDJXzKawD8qgsjKy_a7L7PBbSQ7DDc_DPXI1caYJ6zQ7MdblrJlZjQKxbKVlaH9zJm36l9ITt3kj-GLFFU-ROOx-voNwzGQajN12f7XSqZtPy9m_RfToYT1KaWd6aY8Zns1aQnfqDGnI/s1600/3.png" imageanchor="1"><img border="0" height="363" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMDJXzKawD8qgsjKy_a7L7PBbSQ7DDc_DPXI1caYJ6zQ7MdblrJlZjQKxbKVlaH9zJm36l9ITt3kj-GLFFU-ROOx-voNwzGQajN12f7XSqZtPy9m_RfToYT1KaWd6aY8Zns1aQnfqDGnI/s640/3.png" width="640" /></a><div>
<br /></div>
<br />
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div>
<span style="color: #0000ee;"><span style="caret-color: rgb(0, 0, 238);"><u><br /></u></span></span></div>
<div>
<span style="color: #0000ee;"><span style="caret-color: rgb(0, 0, 238);"><u><br /></u></span></span><div>
<br /></div>
<div>
<br /></div>
</div>
すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0tag:blogger.com,1999:blog-211950077144920547.post-46897532750709364732017-07-27T23:33:00.000+09:002019-03-16T22:42:30.372+09:00複数積んだFragmentBackStackを一度にクリアする方法<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZN7SHdAB-3c1hIlSqko8blUR5hyO026N9gNCC655lXjsW67gR-zyFG29VTQJzK4mDh1Jth-kq3D2mrQPr0e7PTwbSjXZX-EzpjXD2mXFXC-jF9KV9x_Qm3pSF5XV_C67G9_vXnpfjW5A/s1600/_android.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="250" data-original-width="400" height="250" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZN7SHdAB-3c1hIlSqko8blUR5hyO026N9gNCC655lXjsW67gR-zyFG29VTQJzK4mDh1Jth-kq3D2mrQPr0e7PTwbSjXZX-EzpjXD2mXFXC-jF9KV9x_Qm3pSF5XV_C67G9_vXnpfjW5A/s400/_android.png" width="400" /></a></div>
<br />
Androidアプリを作るとき昔はActivityを複数作ってIntentで画面切替をしていた。<br />
今は一つのActivityの中にFragmentを突っ込んでFragmentを切り替えることで画面切替をするようにしている。(一般的にもこの方法が今はメジャーなはず)<br />
<br />
FragmentA、FragmentB、FragmentCと3つのFragmentがあったとして、<br />
Fragmentでの画面切替は普通こんな感じで行う。<br />
<br />
<pre class="prettyprint lang-java">FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.container, FragmentA.newInstance() );
transaction.addToBackStack(null);
transaction.commit();
</pre>
<br />
commit時にBackStackに積んでいるので<br />
[A] -> [B] -> [C] と画面切替をした後バックキーを押す度に<br />
<br />
[C] -> [B]<br />
<br />
[B] -> [A]<br />
<br />
と切り替わる。<br />
<br />
しかし、一度に [C] -> [A]に戻りたいときどうすれば良いかを分らなかった。<br />
<br />
調べたところ、BackStackはFragmentのインスタンスを保存しているのではなく、transactionで行ったことを記録しているだけらしく、popBackStack()が実行されると記録されたことと逆のことが実行される。<br />
<br />
例えば、replace(FragmentB)(中身はremove(FragmentA)してadd(FragmentB))が記録されていた場合<br />
その逆のremove(FragmentB)してadd(FragmentA)が実行されるらしい。<br />
<br />
<br />
知らなかったーーーー。<br />
<br />
それならば、話は簡単で[C] -> [A]に一度のバックキーで戻るにはFragmentBackStackの数が変わったことをイベントトリガとして、BackStackの数が0に成るまでpopBackStackを呼んでやれば良い。<br />
<br />
FragmentBackStackの数の変更はFragmentManager.OnBackStackChangedListnerを実現することで検知できる。<br />
<br />
以下サンプルコード<br />
<br />
<pre class="prettyprint lang-java">public class MainActivity implements FragmentManager.OnBackStackChangedListener{
private int fragmentBackStackCnt = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction().add(R.id.container, FragmentA.newInstance()).commit();
fragmentManager.addOnBackStackChangedListener(this);
}
@Override
public void onBackStackChanged() {
FragmentManager fragmentManager = getFragmentManager();
int count = fragmentManager.getBackStackEntryCount();
if (fragmentBackStackCnt > count && count != 0) {
// backStackが0になる(検索画面になるまで)fragmentをpopする
fragmentManager.popBackStack();
}
fragmentBackStackCnt = count;
}
}
</pre>
<br />
参考ページ:<br />
<a href="http://extra-vision.blogspot.jp/2016/02/android-fragment-transaction-back-stack.html">Android Fragment トランザクション - バックスタックの落とし穴</a><br />
<br />すいかメガネhttp://www.blogger.com/profile/07284305208926198234noreply@blogger.com0