じゅころぐAR/VR

AR/VRメインのブログ。時々ドローン。

ARKitでARモグラ叩きを作ってみた

久し振りのARネタです。簡単かつ応用が効く方法なのでまとめておく。

はじめに

iOS11.3と同時に提供されるARKit1.5で垂直面の検知ができるようになるそうです。
それなら、"以前にTangoで作ったARモグラ叩きが作れるのでは?"と思い、現時点で実装可能なARKit版(水平面のみ)を実装してみました。

上記のTango版は透明なメッシュをリアルタイムで作って、メッシュの下からモグラを表示させています。
同じことをARKitやARCoreでやろうとしてもTangoのようにメッシュを作成するのは容易ではないので、透明な箱に隠しておく実装で代用しました。

開発環境

以下の環境で作りました。ARKit1.5にはまだ対応していません。

  • macOS High Sierra
  • Unity 2017.3.0f2
  • Unity ARKit Plugin version 1.0.14

モグラ役には、こちらも久し振りのクエリちゃんSD版モデルを使います。
クエリちゃんアセットは"Creative Commons Attribution 4.0 international License(CC-BY)"で提供されています。

作り方

Unity ARKit Pluginで基本的なシーンを構成する方法は割愛します。
以下の記事などを参考にしてください。

qiita.com

モグラ叩きの処理は、

  • ランダムにキャラクターを出現させる
  • 平面から出てくるように見せる

の順で実装しています。

ランダムにキャラクターを出現させる

Unity ARKit Pluginに同梱されているExamplesのUnityARBallzで使われているBallMaker.csが参考になります。

上記スクリプトは、"画面をタップして平面にGameObjectを置く処理"なので、これを改変してランダムにモグラを配置していきます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.iOS;

public class GameController : MonoBehaviour {

    public GameObject moguraPrefab;

    private float timeInterval = 2.0f; // モグラの出現間隔(秒)
    private float timeElapsed; // 経過時間を保存する変数

    void Update () {
        // 一定時間が経過したらHitTestを実行する
        timeElapsed += Time.deltaTime;
        if(timeElapsed >= timeInterval) {
            _executeHitTest ();
            timeElapsed = 0.0f;
        }
    }

    private void _executeHitTest() {
        // 画面上のランダムな座標を元にARPointをセット
        var screenPosition = Camera.main.ScreenToViewportPoint(new Vector3 (Screen.width * Random.value, Screen.height * Random.value));
        ARPoint point = new ARPoint {
            x = screenPosition.x,
            y = screenPosition.y
        };

        // HitTestを実行する
        List<ARHitTestResult> hitResults = UnityARSessionNativeInterface.GetARSessionNativeInterface ().HitTest (point, ARHitTestResultType.ARHitTestResultTypeExistingPlaneUsingExtent);

        // 平面と衝突した位置にモグラを出現させる
        if (hitResults.Count > 0) {
            foreach (var hitResult in hitResults) {
                Vector3 position = UnityARMatrixOps.GetPosition (hitResult.worldTransform);
                _createMogura(position);
                break;
            }
        }
    }

    // モグラの生成
    private void _createMogura(Vector3 position) {
        GameObject mogura = Instantiate (moguraPrefab, position, Quaternion.identity);  
    }
}

Update()でタイマー処理を実装し、上記の例では2秒おきにHitTestを実行しています。

_executeHitTest()はExampleのHitTest処理とほぼ同じですが、HitTestを行う座標のscreenPositionを作成する処理が異なります。
Random.valueは0.0〜1.0の乱数を返すため、画面の幅・高さに掛け合わせることで画面上の任意の座標をランダムで取得することができます。

このスクリプトを任意のGameObjectにセットして、moguraPrefabを指定すると、キャラクターが平面上にランダムで出現するはずです。

平面から出てくるように見せる

せっかくARなので、平面からニョキニョキと現れるようにしたいですよね。
これを実現するため、GameObjectを置いた時点ではキャラクターを透明な箱に隠しておき、キャラクターだけを箱の外に移動させるスクリプトを書きます。

まず、親のGameObjectを作成し、Cubeとキャラクターを配置します。

f:id:jyuko49:20180212145247p:plain

次に、以下の位置合わせを行います。

  • 親のGameObjectの位置を(0, 0, 0)で固定
  • キャラクターが収まるようCubeのサイズを調整
  • Cubeの上面がy=0になるようy座標を調整

位置合わせ後はこんな感じになります。調整の際は、CubeのMeshRendererをON/OFFすると確認しやすいです。

f:id:jyuko49:20180212145751p:plain

位置が決まったらCubeのMeshRendererにオクルージョン用のマテリアルをセットします。
"Create" > "Material"で新規マテリアルを作成して、Shaderに"VR/SpatialMapping/Occlusion"を選択します。

f:id:jyuko49:20180212145737p:plain

エディタ上では単に透明な箱ですが、実機ではARカメラ の映像が表示されるため、中身はそこにあるけれど見えない状態になります。

最後にキャラクターだけが箱の外に出てくるスクリプトを追加します。親のGameObjectではなく、箱の中のキャラクターに"Add Component"で適用されるようにします。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MoguraController : MonoBehaviour {

    private float moveDistance = 0.5f; // 移動させる距離
    private float movedDistance = 0f; // 移動した距離
    private float moveSpeed = 0.02f; // 移動させる速さ

    void Update () {
        if (movedDistance < moveDistance) {
            this.transform.position += this.transform.up * moveSpeed;
            movedDistance += moveSpeed;
        }
    }
}

スクリプトを保存したら、親のGameObjectごとPrefab化して、最初に作成した"モグラをランダムに出現させるスクリプト"にmoguraPrefabとしてセットします。

ビルドして実行すると、今度は平面からニョキニョキと生えてくるはずです。

冒頭のTango版では、これに"一定時間でモグラが消える処理""画面タップでボールを投げる処理""ボール衝突時のアニメーション&パーティクル""スコア計算"などを追加して、ミニゲームっぽくしています。

応用編

今回使ったロジックを少し改変すると、色々なシーンで使えます。

平面に消えていく表現

今回作ったモグラの初期状態と終了状態を入れ替えるだけです。
最初の位置合わせをキャラクターが箱の上に乗っている状態にして、箱の中に隠れていくスクリプト(移動方向をマイナスにするだけ)を書けばOKです。

水面に見立てて沈んでいく表現や忍者(床や天井に消えるイメージ)っぽい表現で使えると思います。

徐々に出てくる表現

キャラクターを箱から出すのではなく、箱の方をスクリプトで上下に移動させることで、キャラクターが徐々に転送されてくるような表現になります。GANTZみたいなやつです。

ARはてなボックス

「?」のテクスチャを貼って、宙に浮かせているだけです。箱を出た後に、スクリプトで移動の方向を変えてそれっぽく見せています。
キノコ(の妖精)のMMDが使いたかったため、UnityではなくWebAR(three.ar.js)で実装しました。

まとめ

一見難しいことをやっているようで、方法を思い付いてからは簡単でした。平面検知さえできれば実装でき、プラットフォームを問わないので使い勝手がよいです。

平面の上を動かすだけでなく、平面から出たり消えたりさせることで、ARKitで手軽に現実感を増すことができます。

垂直面を扱う場合、Tango版のように配置する平面の応じてモグラの向きを変える必要があります。これはHitTestの際に衝突した平面の法線(normal)が取得できれば実現可能です。
このあたりはUnity ARKit Pluginが垂直面に対応してから実装します。

Actions on GoogleのIntentsを理解する

少し間が空きましたが、Actions on Googleで対話型アシスタントを自作する作業の続きです。

Google Homeと連携するサービスを自作するには - じゅころぐAR/VR
Actions on GoogleとAmazon API Gatewayを連携させる - じゅころぐAR/VR

今回はアシスタント作成の基本となるIntentsの設定をしていきます。
Intents  |  Dialogflow

Intentsを作成するまでの流れ

Actions on GoogleGoogleアカウントでログインしてプロジェクトを作成すると、以下のような画面になります。

f:id:jyuko49:20180105170817p:plain

Build a custom appの"Dialogflow"をクリックすると、ポップアップが開き、Dialogflowでactionsを作成するよう促されます。

f:id:jyuko49:20171222074632p:plain

ボタンをクリックしてDialogFlowのコンソールに遷移すると、現在登録されているIntentsの一覧が表示されます。

f:id:jyuko49:20171222075443p:plain

既存のIntentを編集する場合は一覧からクリック、追加の場合は、画面左メニューの"+"ボタンもしくは画面上部の"CREATE INTENT"でIntentを追加できます。

Intentを作成する

固定のResponseを返す

最もシンプルな定義は、ある入力 = "User Says"に対して決められた出力 = "Response"を返す設定です。

"User Says"にユーザから入力してもらう文言を入れます。

f:id:jyuko49:20180104231922p:plain

句読点の有無やカナ・漢字などの表記ゆらぎは吸収してくれるので、あまり気にしなくても大丈夫です。

文章は複数定義することができ、いずれかに該当すれば合一のIntentとしてResponseを返します。同じ意味の文章を言い回しを変えて定義しておくと、どれでも反応してくれるようになります。

f:id:jyuko49:20180104233335p:plain

続いて、"Response"を定義します。定型文を返す形であれば、返したいメッセージをそのまま入力すればOKです。

f:id:jyuko49:20180104231935p:plain

Responseも複数の文章が定義でき、定義されたものから一つをランダムで返します。

f:id:jyuko49:20180104234803p:plain

"Use Says"と"Response"を定義したら、画面上部メニューでIntentに適当な名前を付けて"Save"をクリックします。

f:id:jyuko49:20180104235825p:plain

名称の横にある青い点をクリックすると、Intentの優先度を設定することができます。Intentが複数ある場合、スコア(後述)が高いものが使われるため、優先度を上げることでそのIntentが使われやすくなります。

f:id:jyuko49:20180104235853p:plain

Intentを"Save"したら、画面の右側にある"Try it now"で動作確認してみましょう。

テキストボックスに直接入力するか、マイクアイコンをクリックして音声入力ができます。マイクが使えるなら音声入力の方がGoogle Homeでの動作に近い形のテストになります。

テスト実行した例です。

f:id:jyuko49:20180105001700p:plain

入力したテキストは"User Says"に定義した文章のどれとも完全には一致しませんが、スコア(関連性)が高いIntentとして先程定義したものが使用されています。

Intent選択時のスコアは"SHOW JSON"で確認できます。

f:id:jyuko49:20180105124653p:plain

関連性が高いIntentほどスコアが大きくなり、入力が"User Says"と完全一致するとスコアは1になります。
思い通りにIntentが選択されない場合、"User Says"に定義する文章やIntentの優先度で調整します。

Fallback Intentを編集する

Fallback Intentは特殊なIntentで、他のIntentで入力に該当するものがなかった場合の入力エラーとして使われるIntentです。

Dialogflowに最初から定義されている"Default Fallback Intent"を編集します。

f:id:jyuko49:20180105012633p:plain

Fallback Intentには、"Usr Says"の入力欄がありません。優先度の設定も存在せず、Fallback IntentのON/OFFを行うUIになっています。

Responseは入力エラーに相当するメッセージを定義しておきます。

Eventsをトリガにする

"User Says"に反応する定義の他に、Eventsに対してResponseを返す設定ができます。
Fallback Intentと初期設定されている"Default Welcome Intent"Eventsを使った例になっています。

f:id:jyuko49:20180105010119p:plain

この例では"User Says"は定義されておらず、Eventsに"WELCOME"が設定されています。
Google Homeではアシスタントアプリに接続した直後に、"WELCOME"イベントが発生し、定義したメッセージが発話されます。

Parametersを使う

少し高度な設定として、"User Says"からパラメータを抽出して応答に使います。
Actions and Parameters  |  Dialogflow

"User Says"からParameterを抽出

"User Says"に例文を入力した後、パラメータとして抽出したい単語を選択するとエンティティ(後述)の一覧が表示されます。

f:id:jyuko49:20180105113456p:plain

@sys.anyを選択して、PARAMETER NAMEをanyから任意の名称に変更します。(anyのままでもOK)

f:id:jyuko49:20180105113824p:plain

@sysシステムエンティティと呼ばれるDialogflowで予め定義されたデータ入力型です。
例えば、@sys.dateは日付にマッチする入力をISO-8601にフォーマットして返してくれます。

例:「"1月1日"の天気は?」
 → "2018-01-01"をパラメータにセット

@sys.anyは空でない任意の入力にマッチし、マッチした文字列そのものを返します。
System Entities  |  Dialogflow

ActionでParameterの設定を行う

抽出したパラメータの設定はActionで行えます。Action名の設定は任意です。
f:id:jyuko49:20180105120505p:plain

"User Says"で抽出した"PARAMETER NAME"、"ENTITY"を"VALUE"にセットする定義になります。
"REQUIRED"はパラメータを必須入力とするかどうかの設定、"IS LIST"はマッチする値が複数あった場合に配列で返す設定です。

ResponseでParameterを使う

Actionで設定したパラメータの"VALUE"をResponseに埋め込むことができます。
$ではじめる"VALUE"をそのまま記述すればOKです。

f:id:jyuko49:20180105121521p:plain

ここまでの設定を"Save"してテストしてみます。

f:id:jyuko49:20180105123314p:plain

入力した文章からパラメータが抽出され、Responseで使われていることがわかります。
同様にJSONにもアクション名とパラメータの値が追加されています。

f:id:jyuko49:20180105124718p:plain

FulfillmentでWebhookを用いた場合、このJSONに相当する内容がPOSTされるため、Webhook先でもパラメータが使えます。

Entityを定義する

先程は@sys.anyで任意の文字列を抽出しましたが、入力をそのまま返すという特性上、不便な点がいくつかあります。

  • "IS LIST"が使えない
    「"AとB"について」や「"A、B"について」と入力された場合、"A","B"の2つの要素を配列で取得したいところですが、@sys.anyだと区切り文字を判別しないため、"AとB"、"A、B"を返します。

  • 入力のゆらぎに対応しにくい
    「"お肉"食べたい」「"肉"食べたい」は同じ"肉"への欲望を表現していますが、@sys.anyだと"お肉"、"肉"と異なる結果を返します。

これらを解決するため、Entity(エンティティ)を定義します。
画面左のメニューからEntitiesを選択し、"CREATE ENTITY"をクリックで設定画面が表示されます。

f:id:jyuko49:20180105131515p:plain

まず、画面上部のテキストフィールドにエンティティ名を入力します。

次に、"Define synonyms"でシノニムを利用するかのチェックを入れます。
シノニムは同意語の意味で、同じ意味で表現が異なる単語を同じ値として扱います。
リストに追加した行の右側に同意語として扱う単語をカンマ区切りで列挙し、左側にVALUEにセットする文字列を記述します。

最後に、"Allow automated expansion"で自動拡張を許可するかを設定します。
チェックを外すと、リストに定義されている単語のみマッチします。例えば、天気のエンティティ(@weather)を定義して、"晴れ"、"曇り"、"雨"、"雪"のみを扱いたい場合、チェックを外しておくと余計な入力を受け付けません。
チェックを入れると、リストにない入力も受け付けるようになります。こちらは@sys.anyに近い挙動ですが、シノニムと"IS LIST"は有効になります。

設定が完了したら、"Save"をクリックでEntityが作成されます。
今回は、"Define synonyms""Allow automated expansion"をどちらも有効にしました。

定義したEntityをIntentで使う

Intentの編集画面に移動し、"User Says"とActionの@sys.any先程定義したEntity(@friends)に変更します。
また、動作確認のため、"IS LIST"も有効にしました。

f:id:jyuko49:20180105141802p:plain

この設定で再度テストしてみます。

まずはシノニムの確認。元の文字列は"ちゃん"付けになっていますが、"VALUE"はシノニムで定義した値になっています。

f:id:jyuko49:20180105142909p:plain

次に"IS LIST"の確認です。"AとB"、"A、B"のような入力をすると、VALUEはAとBの配列で取得できています。

f:id:jyuko49:20180105143344p:plain

Google Homeでテストする

ここまでの設定で入力から抽出したパラメータを使ったメッセージを返せるようになったので、実機で試してみます。

Actions on Googleの画面に戻り、2のApp informationで"EDIT"をクリックします。

f:id:jyuko49:20180105152828p:plain

いくつか設定項目があるうちのAssistant app name[アシスタントアプリ名]を入力します。このアプリ名はアシスタントに接続する際に使います。

f:id:jyuko49:20180105152834p:plain

入力したら画面下の"SAVE"ボタンで保存します。未入力の項目でエラーが出ますが、アプリをパブリックに公開するための設定が含まれるため、テスト時は無視して問題ありません。

"TEST DRAFT"ボタンをクリックします。

f:id:jyuko49:20180105145602p:plain

ブラウザ上ではSimulatorの画面に遷移しますが、この画面では特に何もしなくても実機でテストできる状態です。

Google Homeに向かって、
「OK、Google! [アシスタントアプリ名]につないで」
と発話してみましょう。

前提として、Actions on Googleで使っているGoogleアカウントと同じアカウントをGoogle Homeと連携させておく必要があります。
上手くいけば、"Default Welcome Intent"で定義したWELCOMEイベント発生時のメッセージが発話されるはずです。

接続ができた後は入力を待ち受ける状態になるので、Intentsのテストを行います。

設定したIntentが動作していることが確認できました。

まとめ

Intentsの基本的な設定を一気に書いたので、かなり長くなりました。
機能が多く、最初のうちはわかりにくさがありますが、ドキュメントを読み進めつつ設定していけば、何が行えるのかを徐々に理解できるかと思います。

まだ使っていない中で重要そうなところではContexts(コンテキスト)という機能があります。
アシスタントと自然な対話を行うために、直前の入力を一時的に保存しておき、その後のIntentsで使う仕組みです。

この辺りまで押さえておけば、対話形式の入力を自由に扱えるようになるので、あとはバックエンドでどんなサービスと組み合わせるかを考えれば、様々なアシスタントを作成・提供することができます。
まあ、サービスとして提供するデータやWeb APIを用意するのが一番大変なんですけどね。

Tangoのサポート終了について考える

発売当初からのTangoユーザとしてポエム書きました。

はじめに

ご存知の通り、GoogleがTangoのサポート終了日を告知しました。初めてのサポート端末Phab2 Proが発売されてからわずか1年しか経っておらず、Tangoが推進するARに将来性を感じていた身としては一歩後退という感じがして、残念でなりません。

ARCoreが発表された時点でTango終了は既定路線だったので別によいですが、サポート終了とあわせて発表されたARCore Preview 2がTangoの機能には遠く及ばない(にも関わらずTangoを終了した)ということの方が意味が大きいです。
Tangoで開発したかったユーザからすると開発期間の断絶が発生していて、ARCoreで継続できるかわからないということ。

何故、Tangoは終了したのだろう?

Googleの発表を見ても、はっきりとしたところはわかりません。
5月のGoogle I/O 2017まではTango終了の兆しはなかったですし、VPS(Visual Positioning System)をTangoベースで構築していくことを発表していました。

唯一の理由らしき文言は、より幅広いデバイスで対応できるよう、特別なセンサなしで使えるARCoreに移行するとのことですが、ARCoreが対応しているのは発表から3ヶ月経った時点でPixelとGalaxyのシリーズのみ。しかも、Android 7.0以降のハイエンド端末しか対応していない点から普及には時間がかかりそうで、言動がチグハグな印象も受けます。
唐突な幕引きには、Google Glassのときと既視感を感じますね。

あくまで想像ですけど、GoogleのフラッグシップモデルであるPixel2にTangoが搭載されなかった(もしくはできなかった)ことが全てを物語っているような気もします。ARCoreを発表した9月の時点でPixel2のデザインは固まっているはずで、そこにTangoに必要なセンサが付いていないとなると、Pixelの開発とTangoプロジェクトが同時に進めない問題を何か抱えてたのかな?とか、Pixel2でも実現できるプラットフォームにしたのかな?とか。
ちなみに、ARCoreの発表直後にPixelの開発に携わっているHTCのスマートフォン事業を買収しているので関係がないとも言い切れない。

いずれにせよ、外部にいる開発者には理由を想像することしかできず、若干モヤモヤした形でTangoは終了となりました。

ARCore、ARKitはTangoの代わりとなるのか?

Tangoの特徴は位置情報を復元できるエリア・ラーニング、高精度のPoint Couldを利用したAPIだと思っています。
Tangoのサポート終了告知と同時にARCoreのPreview 2が発表されましたが、どちらも今のARCoreでは機能提供されておらず、独自の実装でも実現が難しそうです。

ARKitに関しても、True Depthカメラが背面に付いていないと利用は難しいと思っています。iPhone Xは何故か前面にだけ付いていて、画面を見ながら周囲の情報を得ることができない・・・。

個人的な予想では、どちらにも背面にデプスカメラが付くと思っていたのですが、 結果はどちらも見送り。少なくとも次のモデルチェンジ(一年後)までTango相当の機能はお預けになりました。GoogleはZenfone ARでできるだろ!と思いつつも、たぶんやらないでしょう。
ただ、次期iPhone、次期Pixelには背面にTrue Depthカメラが付く可能性も高い(願望込み)と思っていて、そのタイミングでARKit、ARCoreがアップデートされ、Tangoのノウハウは活かせるんじゃないかと期待しています。

仮に同じタイミングでPixelとiPhoneに機能が搭載されたら日本国内ではiPhoneを使うユーザの方が多いでしょうし、今となっては次期iPhoneの方が採用される期待値が高いように思います。
Tangoからの流れとしては、ARCoreではなくARKitをやっておくのが得策かもしれません。

ARクラウドにも期待

エリア・ラーニングに必要な点群(PointCloud)をクラウドデータベース化して共有するVPS(Visual Positioning System)という仕組みですが、TangoからARCoreへの移行と併せて開発が中止もしくは延期したみたいです。

この辺りの技術はARクラウドと呼ばれているようで、Appleも開発を進めているらしい。
ARクラウドに関しては、こちらの記事がよくまとまっている上に情報量が多くてわかりやすいです。 www.moguravr.com

GPSの測位精度が落ちる屋内でも正確な位置を復元できるため、ARコンテンツを永続的にその場所に表示できるようになります。
広告やナビゲーションなどの実用系コンテンツ、位置ゲーなどでARを導入しやすくなるため、普及が期待できそうな気がしています。

おわりに

Tangoは終わるけどARの普及はこれからだし、来年以降にきっと実現するであろう機能を先取りできたと思えば、まあいいかな。(と思うしかない)
とはいえ、Tangoに魅力を感じてARを始めた身として今のARCoreでやりたいことは多くないので、AR以外のところもちょいちょいやっていきます。

最後にTangoへの無念を込めて、Tangoで作ったデモ達を供養しておきます。
クエリちゃん率高し。

ボール投げたらぶつかる

ボールじゃなくてクエリちゃん!

点群データが取れる

ARKit、ARCoreはマーカー検知できないんですよね

困った時のマーカー検知。あってもいいと思うんですけど。

壁検知もできないですね

ARモグラ叩き的なやつ。Tangoだと超簡単に作れます。

キャラクターが現実世界を歩き回る

クエリちゃんズ!もARCore版は難しいですね。

送り火

Tangoよ、安らかに眠れ。

Actions on GoogleとAmazon API Gatewayを連携させる

前回に引き続き、Google Home向けのサービスを作って遊びます。
今回はFulfillmentを使って、AWSにWebhookさせてみます。

Fulfillment  |  Dialogflow

概要

処理のフローは以下になります。

Google Homeに話しかける
→ Actions on GoogleのAppを呼び出し
→ Appにメッセージ送信
→ DialogflowのIntentsからFulfillmentでWebhook
→ Webhook先のAmazon API Gatewayで処理を行う
→ 結果(発話・テキスト)をDialogflowに返す

Google Homeに話しかけてDialogflowで定型文を返すところまでは簡単にできるので、FulfillmentでAmazon API Gatewayと連携する部分を作ります。

Fulfillmentを設定する

外部サービスへのWebhookを行うには、Fulfillmentを設定します。
Dialogflow内にメニューがあります。

f:id:jyuko49:20171213002917p:plain

とりあえずは連携だけ確認したいので、Webhook先のURLだけ設定します。
URLは後述のAmazon API Gatewayで作成したものです。

f:id:jyuko49:20171213004124p:plain

画面の下の方にSaveボタンがあるので、忘れずに更新してください。

Amazon API GatewayAPIを作る

API GatewayAWSの各サービスや外部のWebAPIをエンドポイントとして、APIを提供できるサービスです。

AWSアカウントでログインし、API Gatewayのコンソール画面を開いたら、まず[APIの作成]を行います。
API名はリクエストURLには含まれないので、わかりやすい名称なら何でもOK。その他は任意入力。

f:id:jyuko49:20171213005948p:plain

APIが作成されたらリソース、メソッドを作ります。リソース、メソッドに付いては一般的なRESTの概念なので、この場では説明を割愛。

アクション > メソッドの作成(リソースの作成)で追加できます。今回はリソースを作らず、ルート直下にPOSTメソッド(FulfillmentでのWebhookがPOSTのため)だけ作りました。

f:id:jyuko49:20171213012612p:plain

メソッドを作成したらエンドポイントにつなぎます。
設定次第でいろいろなサービスと連携できるのですが、今回はMockで固定値を返すようにします。

f:id:jyuko49:20171213013852p:plain

Mockの出力は統合レスポンスブロックの本文マッピングテンプレートになります。
FulfillmentのResponse仕様に沿って、レスポンスを整形します。

f:id:jyuko49:20171213014949p:plain

ここまで設定できたら、テスト実行が行なえます。出力固定のMockなので、何も入力せずに[テスト]ボタンを押して、レスポンス本文がマッピングテンプレートの通りになっていれば正しく設定できています。

f:id:jyuko49:20171213015229p:plain

テストが期待通りになっていたら、アクション > APIのデプロイで外部からアクセスできるようにします。

デプロイはステージを指定するようになっているので、運用前ならtest、betaなど、本運用に入ったらv1,v2,v3,...と切っていくと並行運用ができます。
デプロイメントの説明に書いた内容はデプロイ履歴に一覧で残りますし、デプロイ履歴からのロールバックもワンクリックでできるので、書いておくと便利です。

f:id:jyuko49:20171213004102p:plain

デプロイが完了すると、作成したステージに呼び出し用のURLが表示されます。
東京リージョン(ap-northeast-1)で作ったのであれば、以下になっているはず。

https://[ランダムな文字列].execute-api.ap-northeast-1.amazonaws.com/[ステージ]/([リソース]/...)

このURLをFulfillmentに設定してあげれば、Dialogflowから叩けるようになります。

Intentsを設定する

ここまでできれば、あと少し。
Dialogflowに戻って、Intentsを追加します。

User saysにメッセージを定義して、Fulfillmentの"Use webhook"にチェックを入れます。

f:id:jyuko49:20171213022535p:plain

レスポンスはWebhookで返すので、Responseの欄は何も入力しなくてOKです。
設定したらSaveを忘れずに。

Actions on Googleでテストする

Dialogflowで設定したActionをActions on GoogleのSimulatorでテストします。
マイクが使えない場合はキーボード入力で確認できます。

f:id:jyuko49:20171213021747p:plain

IntentsのUser saysに設定したメッセージを送信すると、Amazon API Gatewayに定義されているMockのメッセージが発話・テキスト表示されました!

まとめ

FulfillmentによるWebhookは連携URLを指定するだけで簡単です。
リクエスト先では指定のフォーマットでレスポンスを返す必要があり、AWSを利用するのであれば、API GatewayやLambdaファンクションで整形してあげるとよいです。

Mockではなく、エンドポイントをAWSサービスやWebサービスとの連携にすれば、対話形式での検索やデータベースへの登録もできるし、セッションが管理できればインターネットでの予約・購入など、割と何でもできそう。
今はまだ定型文のメッセージでしか操作していないので、Entities、Actions and Parameters、Eventsなどを理解して使いこなせるようになりたい。

Google Homeと連携するサービスを自作するには

半額だったのでGoogle Home Mini買ったけど、何ができるかちゃんと調べてなかったので自分用メモ。

Google HomeからSNSに投稿する

IFTTT(イフト)を使うと、プログラミングなしでできるようだ。
ちなみに、アイエフティーティーティーだと思ってました。

yuki-no-yabo.com

Node.jsでGoogle Homeに通知を送る

google-home-notifierを使って、Google Homeにpush通知する使い方。

github.com

### 適当なディレクトリを作成して移動
$ mkdir googole-home
$ cd googole-home

### npmでインストール
$ npm init
$ npm install google-home-notifier

### vimでテスト用のスクリプト作成
$ vim index.js

index.jsはこんな感じです。メッセージと言語を変えた以外は、ほぼサンプルそのまま。

const googlehome = require('google-home-notifier')
const language = 'ja';

googlehome.device('Google-Home', language);

googlehome.notify('テストだよ', function(res) {
  console.log(res);
});

nodeコマンドで実行してみた。

$ node index.js
Device "Google-Home-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" at 192.168.0.5:8009
Device notified

実行して数秒後、Google Homeで通知音が鳴って、"テストだよ"と発話されました。
同一ネットワーク上のデバイスが自動で検出されて、名前に"Google-Home"を含む場合にpush通知が行われるようです。数秒のタイムラグはデバイスの検出にかかる時間も大きいと思われる。

利用シーンとしては、README.mdのListenerに書いてある手順でMacかラズパイにローカルサーバ立てといて、屋内のセンサデバイスからサーバにメッセージをPOST、Google Homeに発話させる形がよさそう。

Actions on Googleでチャットbotを作る

Dialogflowとやらを使うと、Google Homeが認識したメッセージに任意の応答を返せるようです。

つい最近までapi.aiという名称だったようで、以下の記事の手順で設定できました。 qiita.com

定型文に定型文を返すだけのbotは簡単にできます。

Fulfillmentを使えば、DialogflowからのWebhookで結果を返すこともできるらしい。
Fulfillment  |  Dialogflow

Dialogflowだけでもメッセージを複数パターン用意できるけど、WebhookすればDBやAIを使えるのでより柔軟な応答ができるはず。

Webサービスはpublicに公開されていてHTTPSで応答できないとダメなので、クラウドで立てるのが楽そうです。GoogleのドキュメントにはFirebaseやHerokuにデプロイする方法が書いてあります。たぶんAWSでやるけど。

WebAR-Articleを動かしてみた

WebARに注目して定点観測しているGoogle ARGithubプロジェクトにWebAR-Articleなるリポジトリが追加されていました。
実際に動かしてみたところ、WebサイトにARコンテンツを埋め込むサンプル(Google非公式扱い)でした。

github.com

動かしてみる

READMEのGetting Startedに従って、インストール&実行していきます。

Macのターミナルを起動し、以下のコマンドを実行します。
実際にはインストールログがたくさん出ます。

$ git clone https://github.com/google-ar/WebAR-Article.git
$ cd WebAR-Article/
$ npm install

無事インストールができたら、npm run devで実行してみます。

$ npm run dev
...
webpack: Compiled successfully.

上記のメッセージが出たら、webpackでローカルサーバが立ち上がっています。この状態のままブラウザ(Chrome等)でhttp://localhost:8000/public/にアクセスするとWebページが閲覧できるはずです。
プロセスを停止したいときは、ターミナルでCtrl+Cです。

PCブラウザで見ると以下のようなWebページになっています。 f:id:jyuko49:20171206124957p:plain

宇宙服の3Dモデルが表示されているcanvas(画像の右下)でthree.js,three.ar.jsが使われています。
フルスクリーンボタンをクリックすると全画面で3Dモデルをグリグリ動かすことができ、✕ボタンで元の記事に戻ります。 f:id:jyuko49:20171206231230p:plain

WebARブラウザで見る

現状ChromeSafariにWebARの機能は実装されていないため、Google ARからWebARブラウザをインストールする必要があります。

各ARプラットフォームに対応したスマートフォンを用意してブラウザアプリをインストールします。
ARCore、Tangoはリンク先からAPKファイルを使ってインストールするだけでOKで、ARKitだけXcodeでのビルド&インストールが必要です。

jyuko49.hatenablog.com

WebARブラウザをインストールしたら、MacのローカルIPを使い、先程立ち上げたローカルサーバに同一のローカルネットワークから接続します。
MacのIPは、ターミナルでifconfigを実行すれば調べられます。

$ ifconfig
...
    inet 192.168.0.4 netmask 0xffffff00 broadcast 192.168.0.255
...

localhostの部分をこのIPアドレスに変えて、http://192.168.0.4:8000/public/をアドレスバーに入力すると、WebARonTangoでWebAR-ArticleのWebページが見えました。 f:id:jyuko49:20171206223323j:plain

canvasには、WebARブラウザで見たときだけ、AR閲覧用のボタン(右下)が表示されています。

このボタンをタップするとARモードに切り替わり、平面に3Dモデルを置いて見ることができます。フルスクリーンと同様に✕ボタンで元記事に戻れます。 f:id:jyuko49:20171206223240j:plain

ざっくり構成を理解して使う

WebAR-ArticleはApache 2.0ライセンスのため、ライセンス表記さえ守ればで、一部改変して利用ができます。

サンプルのWebページはpublic/index.html内でindex.min.jsを読み込んでおり、id="arapp"を付与したcanvasにWebAR対応コンテンツを埋め込んでいます。
その他の部分は、通常のWebと同様に記事を書けばよさそうです。

<canvas id="arapp" height=400></canvas>
<script src="js/index.min.js "></script>

canvasに表示するコンテンツの実体はsrc以下のスクリプトで、npm run buildを実行するとwebpackでバンドル・圧縮されて/public/js/index.min.jsが作られます。

$ npm run build

開発の手順としては、

src以下のソースを改変
npm run build
→index.htmlでindex.min.jsをinclude
canvas要素(id="arapp")を追加 →public以下のファイルをWebで公開

になります。

srcに手を入れると言ってもそこそこ複雑な作りなので、three.jsに詳しくなければ、以下の3ファイルをざっと見て、3Dモデルを差し替えて使う程度がベターだと思います。

  • src/index.js
    コンテンツを表示するcanvas要素のタグを定義しています。実質的なメイン処理はApp.jsです。
  • src/app/AppBase.js
    App.jsの基底クラスです。three.ar.jsを使って3Dシーンの基本構成を行なっています。
  • src/app/App.js
    シーンを構成するコンテンツをセットアップするメイン処理です。

3DモデルはApp.jsのloadMaterialloadModelでloadしており、このメソッド内に記載されているファイルパスを書き換えれば変更できます。

import DRACOLoader from '../../third_party/draco/DRACOLoader';

...

  loadMaterial = () => {
    let mtlLoader = new THREE.MTLLoader();
    mtlLoader.setPath('../public/models/Astronaut/');
    mtlLoader.load('Astronaut.mtl', materials => {
      materials.preload();
      this.loadModel(materials.materials.Astronaut_mat);
    });
  };

  loadModel = material => {
    let dracoLoader = new DRACOLoader('../third_party/draco/', {
      type: 'js',
    });

    dracoLoader.load('../public/models/Astronaut/Astronaut.drc', geometry => {
      geometry.computeVertexNormals();

      this.setupModel(new THREE.Mesh(geometry, material));
      this.setupModelShadow();
      this.setupLights();
      this.setupARControls();
      this.setupAREffects();
      this.setupReticle();
      this.setupModelTween();
      this.parseURL();
      this.HUD.hideLoadingIndicator();
    });
  };

ファイルフォーマットにDracoが使われているので、別のフォーマットを使いたい場合はパスの変更だけでなく、loaderも切り替える必要があります。
three.jsのリポジトリサードパーティ製ライブラリが揃っているので、必要なライブラリをimportして使えば問題ないと思います。

所感

Webサイトの記事にAR対応の3Dコンテンツを埋め込んで使う利用方法は、画像やブラウザ内の3D表示よりも実物をイメージしやすくてよいと感じます。
ショッピングサイトやニュースなど、利用シーンも想像しやすいです。

ただ、WebAR全般の課題としてARCore/ARKitの対応デバイスが普及し、機能がChromeSafariに標準搭載されてくれないと利用者が限定されてしまうため、実際にこのような使い方ができるのは、もう少し先の話になりそうです。

また、このサンプルだとthree.jsやwebpackを使えないとWebARコンテンツが変更できないので、A-FrameのようにHTMLレベルでモデルデータを指定して埋め込めるとより手軽に使えてよいですね。

Lenovo Mirage AR ヘッドセットでthree.jsをホログラム表示する

ジェダイ・チャレンジを購入して手に入れたLenovo Mirage AR ヘッドセットは、スマートフォンの画面で表示されている映像をホログラムで表示してくれます。
f:id:jyuko49:20171202133009j:plain

このヘッドセットを使って、自作のコンテンツをホログラム表示してみました。

ホログラム表示するには

Mirage AR ヘッドセットはトレイに入れたスマートフォンの画面をミラーで反射させています。
スマートフォンで表示されているコンテンツは、モバイルVRでよく使われている視差効果の付いた背景が黒のステレオ画面です。

また、トレイに入れると画面の一部が隠れるため、穴が開いている領域を左右に二分割したコンテンツを表示させる必要があります。 f:id:jyuko49:20171202161839j:plain

実装方法

上述の表示ができれば、プラットフォームは何でもよいのですが、慣れているWebでやってみました。

three.jsでステレオ表示を行う

Webでの3D表示にはthree.jsを使っています。
downloadで保存されるzipファイルを展開して、htmlファイルと同じ場所に置いておきます。

今回はステレオ表示を行うためthree.min.jsの他にStereoEffect.jsも使います。

<script src="three.js-master/build/three.min.js"></script>
<script src="three.js-master/examples/js/effects/StereoEffect.js"></script>

通常のレンダリングではanimate()に相当する処理でrenderer.render()を実行しますが、今回はStereoEffectを通します。

使い方は簡単で、まずStereoEffectrendererを渡します。
.setEyeSeparation()で左右の映像に視差を付けることができます。

effect = new THREE.StereoEffect(renderer);
effect.setEyeSeparation(0.5);

上記で初期化は完了で、renderer.render()の代わりにeffect.render()を実行すれば、ステレオで表示されます。 f:id:jyuko49:20171203224003j:plain

背景はデフォルトで黒なので、そのままにしておきます。

PWAで全画面表示にする

ステレオ表示はできたので、トレイに合わせて描画位置を調整したいのですが、ブラウザのアドレスバーが邪魔で上手く位置が調整できません。
そこで、PWA(Progressive Web Apps)を使って、全画面表示にします。

PWAはざっくり言うと、Webアプリをネイティブアプリのように動かす仕組みです。
Progressive Web Apps

Chromeで"ホーム画面に追加"を行い、ホーム画面のアイコンから起動することでPWAとして動作します。 f:id:jyuko49:20171203232420j:plain f:id:jyuko49:20171203232344j:plain

PWAとして動作させることで、全画面表示ができるようになります。プラットフォームによって対応状況が異なり、設定方法は複数あります。

今回はChromeを使って、Web App Manifestで設定しました。
全画面表示&横画面となるようにmanifest.jsonを作成してhtmlからリンクします。

<link rel="manifest" href="manifest.json">

これでアドレスバーが非表示になりました。 f:id:jyuko49:20171203234333j:plain

描画位置を調整する

最後にトレイの位置に合わせて描画位置を調整します。

描画領域の大きさはrenderer.setSize()で変更できます。

var width = 630; //端末に合わせて調整
var height = 275; //端末に合わせて調整
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( width, height );
document.body.appendChild( renderer.domElement );

描画領域の位置はCSSで調整しました。
renderer.domElementで追加されるのはcanvas要素なので、canvasにpaddingを付けることで調整できます。

canvas {
  padding-left: 76px; /* 端末に合わせて調整 */
}

端末によって必要な余白が異なるため、実際にトレイに入れながら微調整しました。 f:id:jyuko49:20171204000117j:plain

調整できたらbodyの背景も黒にしておきます。 f:id:jyuko49:20171203224104j:plain

試してみる

実際にMirage AR ヘッドセットで見てみるとホログラム表示になりました。

まとめ

three.jsのステレオ表示を使えば、Mirage AR ヘッドセット向けのコンテンツが簡単に作れます。

rendererのサイズと位置を調整して、StereoEffectをかけているだけなので、シーンの作成は通常のthree.jsと同様に行えます。
three.jsに慣れていれば、コンテンツの差し替えは容易です。 f:id:jyuko49:20171203224147j:plain コロコロ (c) gdgd妖精s (ぐだぐだフェアリーーーズ), 2代目gdgd妖精s

現在はホログラムで表示させているだけでヘッドセットが動いても見え方が変わりませんが、スマートフォンの回転やBluetooth接続した機器の情報は使えるので、作り込めば色々できそうな気はします。