2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Bot Framework Composer と Web Chat で 3D モデルを WebXR (AR/VR) 出力する

Last updated at Posted at 2020-07-12

Bot Framework Composer で簡単に作成できる Web Chat ボットに、WebXR を使って 3D モデルを表示する機能を追加する方法を紹介します。

Bot Framework Web Chat の標準機能では、まだ 3D モデルを扱えないため、ボットから Attachment として glTF/GLB フォーマットのデータを返却し、Web Chat 側で React Component を追加してハンドルします。3D モデルの表示と AR/VR 出力部分は <model-viewer>Babylon.js を使います。

よかったらデモ動画をご覧ください。また、https://edgewatcher.azurewebsites.net/webxrbot.htm で実際に試せます。

前準備

以下を利用できるように準備します。Azure のアカウントをお持ちでない方は、Azure 無料アカウント作成から試用できます。

Azure Bot Service については、@chomado さんの完全無料でボットを作る記事、Composer の導入や使い方については、@kenakamu さんのボット開発の記事がわかりやすいです。また、私が作成した Composer の解説動画もあるので、よかったらこちらもどうぞ。

ボットのダイアログ

Composer でボットのプロジェクトを作成したら、ボットの応答 (Bot Responses) に以下のテンプレートを追加します。

# WebXRCard(mode, url, alturl, description)
[Activity
    Attachments = ${ActivityAttachment(json('{
      "mode": "' + mode + '",
      "url": "' + url + '",
      "alturl": "' + alturl + '",
      "description": "' + description + '"
    }'), 'model/gltf-binary')}
]

続いて、ボットの応答で 3D モデルを返却したい部分で、以下のように記述します。第 1 引数で 'ar' もしくは 'vr' を指定します。第 2 引数で glTF/GLB フォーマットのファイルを指定します。iOS での AR 出力に対応したい場合は、第 3 引数に USDZ フォーマットのファイルも用意して指定します。

- ${WebXRCard('ar', 'https://edgewatcher.azurewebsites.net/balustervase.glb', 'https://edgewatcher.azurewebsites.net/balustervase.usdz', 'スミソニアーンの伝説の壺')}

ダイアログのデザイン

ボットの実行と Direct Line チャネルの登録

作成したボットをローカルで実行して Bot Framework Emulator でテストすると、3D モデルの応答部分で以下のように表示されるはずですが、問題ありません。エミュレーターでは扱えないフォーマットであるためです。

エミュレーターでテスト

このボットを Web Chat (Direct Line) で接続できるようにするために、Azure Bot Service に公開します。Composer から直接 Azure にデプロイしてもよいのですが、Azure にホストせず、ローカルで実行しているボットを ngrok を使用して一時的に Bot Channels Registration で公開することも可能です。いずれかの方法でボットを公開し、Direct Line チャネルを登録してシークレットをメモしておきます。

Direct Line

Web Chat ページの作成

Bot Framework Web Chat コンポーネントは、高度なカスタマイズが可能な、Web ベースの Bot クライアントです。React ベースで開発されており、自分でビルドすることもできますが、CDN でビルド済みの JavaScript を公開しているので、ビルド環境を用意しなくても使えます。

3D モデルの Attachment をハンドルする部分を React で拡張するため、このサンプルをベースにカスタマイズします。初めに、3D モデルの表示に必要な <model-viewer> と Babylon.js を <head> タグ内でインポートしておきます。

index.html
    <!-- model-viewer -->
    <script src="https://unpkg.com/@webcomponents/webcomponentsjs@2.1.3/webcomponents-loader.js"></script>
    <script src="https://unpkg.com/intersection-observer@0.5.1/intersection-observer.js"></script>
    <script src="https://unpkg.com/resize-observer-polyfill@1.5.0/dist/ResizeObserver.js"></script>
    <script src="https://unpkg.com/fullscreen-polyfill@1.0.2/dist/fullscreen.polyfill.js"></script>
    <script src="https://unpkg.com/@magicleap/prismatic/prismatic.min.js"></script>
    <script src="https://unpkg.com/focus-visible@5.0.2/dist/focus-visible.js" defer></script>

    <script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.js"></script>
    <script nomodule src="https://unpkg.com/@google/model-viewer/dist/model-viewer-legacy.js"></script>

    <!-- Babylon.js -->
    <script src="https://code.jquery.com/pep/0.4.2/pep.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.2/dat.gui.min.js"></script>
    <script src="https://cdn.babylonjs.com/ammo.js"></script>
    <script src="https://cdn.babylonjs.com/cannon.js"></script>
    <script src="https://cdn.babylonjs.com/Oimo.js"></script>
    <script src="https://cdn.babylonjs.com/libktx.js"></script>
    <script src="https://cdn.babylonjs.com/earcut.min.js"></script>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <script src="https://cdn.babylonjs.com/inspector/babylon.inspector.bundle.js"></script>
    <script src="https://cdn.babylonjs.com/materialsLibrary/babylonjs.materials.min.js"></script>
    <script src="https://cdn.babylonjs.com/proceduralTexturesLibrary/babylonjs.proceduralTextures.min.js"></script>
    <script src="https://cdn.babylonjs.com/postProcessesLibrary/babylonjs.postProcess.min.js"></script>
    <script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.js"></script>
    <script src="https://cdn.babylonjs.com/serializers/babylonjs.serializers.min.js"></script>
    <script src="https://cdn.babylonjs.com/gui/babylon.gui.min.js"></script>

これから作成する React Component 部分は、別ファイルに分けた方がわかりやすいため、同じく <head> タグ内で以下のように定義します。

index.html
    <script type="text/babel" src="webxrcard.js"></script>

続いて、ボットの応答をインターセプトし、'model/gltf-binary' の Attachment として返却されたデータをハンドルします。サンプルの async function() の部分に以下のコードを追加します。<WebXRCard> の部分を React Component として webxrcard.js に実装します。

index.html
    <script type="text/babel" data-presets="es2015, react, stage-3">
      (async function() {

        const attachmentMiddleware = () => next => card => {
          switch (card.attachment.contentType) {
            case 'model/gltf-binary':
              return (
                <WebXRCard mode={card.attachment.content.mode} src={card.attachment.content.url} ios-src={card.attachment.content.alturl} alt={card.attachment.content.description} />
              );

            default:
              return next(card);
          }
        };

<ReactWebChat> コンポーネントに、追加した attachmentMiddleware を指定します。また、公式のサンプルでは、Direct Line のトークンを Web サービスから取得する実装になっていますが、テスト目的であれば、先ほどチャネル登録時にメモした Direct Line のシークレットを直接指定できます。直接シークレットを指定する場合は、以下のように変更します。'YOUR_DIRECT_LINE_TOKEN' の部分をメモした文字列に置き換えます。

index.html
        window.ReactDOM.render(
          <ReactWebChat
            attachmentMiddleware={attachmentMiddleware}
            directLine={window.WebChat.createDirectLine({ token: 'YOUR_DIRECT_LINE_TOKEN' })}
          />,
          document.getElementById('webchat')
        );

また、トークンを取得する部分の処理は削除しておきます。

index.html
/*
        const res = await fetch('https://webchat-mockbot.azurewebsites.net/directline/token', { method: 'POST' });
        const { token } = await res.json();
*/

WebXRCard コンポーネントの実装

別ファイルに分けた webxrcard.js に、コンポーネント部分を実装します。今回は AR の場合は <model-viewer> を、VR の場合は Babylon.js を使用しています。3D モデルを表示するだけであれば、チュートリアルで解説されているレベルのシンプルなコードで実現できます。コード全体を掲載します。

webxrcard.js
function getUniqueStr(myStrong) {
    var strong = 1000;
    if (myStrong) strong = myStrong;
    return new Date().getTime().toString(16) + Math.floor(strong * Math.random()).toString(16);
}

function prepareBabylonCanvas(id, src) {
    if ((id == null) || (src == null)) { return; }

    var canvas = document.getElementById(id);

    var engine = null;
    var scene = null;
    var sceneToRender = null;
    var createDefaultEngine = function () { return new BABYLON.Engine(canvas, true, { preserveDrawingBuffer: true, stencil: true }); };

    var delayCreateScene = function () {
        // Create a scene
        var scene = new BABYLON.Scene(engine);
        scene.createDefaultCameraOrLight();

        // Append glTF model to scene
        Promise.all([
            BABYLON.SceneLoader.AppendAsync(src)
        ]).then(function () {
            scene.createDefaultCamera(true, true, true);

            // The default camera looks at the back of the asset
            // Rotate the camera by 180 degrees to the front of the asset
            scene.activeCamera.alpha += Math.PI;

            const env = scene.createDefaultEnvironment();

            // Here we add XR support
            const xr = scene.createDefaultXRExperienceAsync({
                floorMeshes: [env.ground]
            });
        });

        return scene;
    };

    var engine;

    try {
        engine = createDefaultEngine();
    } catch (e) {
        console.log("the available createEngine function failed. Creating the default engine instead");
        engine = createDefaultEngine();
    }

    if (!engine) throw 'engine should not be null.';

    scene = delayCreateScene();
    sceneToRender = scene;

    engine.runRenderLoop(function () {
        if (sceneToRender) {
            sceneToRender.render();
        }
    });

    // Resize
    window.addEventListener("resize", function () {
        engine.resize();
    });
}

function WebXRCard(props) {
    if (props.mode == "ar") {
        return <model-viewer {...props} auto-rotate autoplay camera-controls ar magic-leap></model-viewer>;
    } else if (props.mode == "vr") {
        const id = "renderCanvas-" + getUniqueStr();
        setTimeout(() => prepareBabylonCanvas(id, props.src), 1000);
        return <canvas id={id} className="renderCanvas" title={props.alt}></canvas>;
    }

    return <div aria-hidden="true" className="markdown css-1b7yvbl"><p>Invalid mode!</p></div>;
}

作成した Web Chat ページのテスト

以上で完成です。作成した index.html と webxrcard.js を適当な Web サーバーに配置して、ブラウザーからアクセスします。ローカル ファイルのままでは、React による動的なスクリプトの参照や、3D モデルのデータ取得ができないため、Web サーバーに配置して試す必要があります。

Web Chat ページのテスト

Astronaut by Poly, licensed under CC-BY.

2
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?