MercariARハッカソンに参加してきた

こんにちは。最近週末は東京、福岡、岡山と飛び回っていることが多く、部屋がちーとも片付かないで困っています。乾燥機じゃなくて天日で布団干したいです。

MercariAR ハッカソン

さてさて、先週「MercariARハッカソン」に参加してきました。

vrtokyo.connpass.com

ハッカソン自体あまり経験がない上に、東京で、しかも最新技術のARで、たった一日で。。。
我ながら無謀なチャレンジだったなぁと。

一人では心細いし、その場で即興チームを組んでみました。そしたらなんとWebARやらA-Frameやらを知っている人(レア)。しかも同じ福岡の人!! とても心強い。

ハッカソンの様子とかは相方さんのブログがよく書かれているのでそちらをどうぞー

bibinbaleo.hatenablog.com

なので僕の方は、作ったアプリの紹介と実装について書いてみようと思います。
10時間突貫でつくりっぱソースなので、汚いのはご容赦願います。。。

作ったもの

まずはアイデア出しから始めたのですが、どうにも苦手でして。相方さんとうーんうーんと悩んでひねり出して、早一時間。ふと、
「メルカリってことで、商品紹介文をARで表示したら面白そうだね。」
「録画していろいろな角度から見せられたら説明の説得力が上がるね。」
ってことで、商品に吹き出し的なものをARで表示してみようとなりました。

作ったもの  → https://daffodil-bell.glitch.me/

f:id:sgi-don:20171219232551p:plain

専用ブラウザが必要なので、お使いになる際は以下を参考にセットアップ願います。

ソースコード

github.com

使った技術

相方さんには申し訳なかったですけど、私はUnityが使えない人間なのでWebARで頑張らせてもらいました。

WebARonARCore/ARKit/Tango をベースとして、フレームワークにA-Frameを使用。開発環境及び動作環境にglitch.comを使いました。A-Frameのコンポーネントとして、

を使っています。

環境設定からaframe-arの使い方については、吉永さん(師匠(非公認(多分怒られる)))のブログが参考になります。

tks-yoshinaga.hatenablog.com

WebARが使える環境になったところで、具体的に実装したところの説明をしていきます。

実装箇所

やってることは大したことなく

  1. 床面を認識してマーカーを表示する。
  2. 始点を選択する。
  3. 終点を選択する。
  4. コメントを入力して表示する。

順に説明していきます。

床面を認識してマーカーを表示する

index.htmlのL149~L173が実装部分です。

    // リングの位置を更新する。
    ar_raycaster.addEventListener('raycaster-intersection', function(evt) {
      var pos = evt.detail.intersections[0].point; 
      mark.setAttribute('color', 'green');
      if (line) {
        // 線描画中
        var box = document.querySelector("#sample");
        var boxPos = box.object3D.getWorldPosition();
        mark.setAttribute("position", boxPos.x+" "+boxPos.y+" "+boxPos.z);
        AFRAME.utils.entity.setComponentProperty(line, 'line.end', boxPos.x+" "+boxPos.y+" "+boxPos.z);        

      } else {
        // 線描画中以外
        mark.setAttribute('position', pos); 
      }
      
    });

    // トラッキングが外れたらマーカーを赤くする。
    ar_raycaster.addEventListener('raycaster-intersection-cleared', function (evt) {
      if (!line) {
        // Turn the mark red.  FIXME: lerp position
        mark.setAttribute('color', 'red');
      }
    });

2つのイベント内にマーカーの処理を書いていきます。

raycaster-intersection

認識した床面とraycasterが交わる座標をとってきて、マーカーの位置としています。これで床面を這うようにマーカーが移動します。線描画中は、スマホの位置・向きを基に、マーカーの位置を取得しています。

今気が付いたけど、これってraycaster-intersection内で実装しちゃだめじゃん。
床面取れなくなったら(raycaster外れたら)位置取れなくなる。
カメラにregisterComponent付けて追随させるのがよいかな。

raycaster-intersection-cleared

raycasterが外れてしまった場合、マーカーの表示を赤色にして外れたことがわかるようにします。

上の実装はコメツケAR用に線描画とかいろいろと書いてあるので、シンプルな実装を確認したい人は、aframe-arのGithubにあるReadme.mdに記載のサンプルを参考にするとよいと思います。

github.com

始点を選択する

index.htmlのL89~L110が実装部分です。

    window.addEventListener('click', function(evt) {
      if (!line && !endPos) {
        // 線描画スタート
        startPos = mark.getAttribute("position").clone();
        startCameraPos = document.querySelector('[camera]').object3D.position.clone();
        line = document.createElement("a-entity");
        line.setAttribute("mixin","lineObj");
        AFRAME.utils.entity.setComponentProperty(line, 'line.start', startPos);
        AFRAME.utils.entity.setComponentProperty(line, 'line.end', startPos);
        scene.appendChild(line);
        
        if (!box) {
          var camera = document.querySelector("[camera]");
          box = document.createElement("a-box");
          box.setAttribute("id","sample");
          box.setAttribute("position","0 0 " +startPos.distanceTo(startCameraPos)*-1);
          box.setAttribute("scale", "0.01 0.01 0.01 ");
          box.object3D.visible = false;
          camera.appendChild(box);          
        }

        mark.object3D.visible = false;

線描画は THREE.Line を使って書いています。始点、終点、色を設定するとサッと線を描いてくれる便利な子。

line – A-Frame

線描画中がわかるようにマーカーを非表示にして、変わりに適当なBOXを配置しています。
このBOXは線描画終了後に削除する予定だったけど、消す実装忘れてた。。。

線描画中は変数の「line」にエンティティが入っているので、上で説明したマーカー位置特定の際に以下のロジックに入ります。

      if (line) {
        // 線描画中
        var box = document.querySelector("#sample");
        var boxPos = box.object3D.getWorldPosition();
        mark.setAttribute("position", boxPos.x+" "+boxPos.y+" "+boxPos.z);
        AFRAME.utils.entity.setComponentProperty(line, 'line.end', boxPos.x+" "+boxPos.y+" "+boxPos.z);        

線描画中のマーカー位置情報は、床面に沿ってではなく、BOXの位置(カメラから見て常に真ん中の一定距離)に配置しています。LINEの終点も同様にBOX位置を指定します。

終点を選択する

index.htmlのL112-L116です。

      } else if (line && !endPos) {
        // 日本語入力中
        line = null;
        endPos = mark.object3D.position.clone();
        document.querySelector("#commentText").focus();

状態判定用にライン変数(line)をクリア、および終点位置を変数(endPos)に格納しています。そのままコメント入力できるよう、テキストボックスにフォーカスを合わせています。

コメントを入力して表示する

A-Frameには<a-text>~</a-text>という文字表示エンティティがあるのですが、マルチバイト文字非対応という残念無念。
さてどうしようと思ったら、相方さんがサクッと参考になるサイトを見つけてくれました。すごいね!!

vr-lab.voyagegroup.com

まずは、描画用のHTMLソース。

index.htmlのL74-77

    <!-- HTML2Canvas -->
    <div style="background: rgba(255,255,255,0.01);color: #333; width: 100%; height: 100%; position: fixed; left: 0; top: 0; z-index: -1; overflow: hidden">
      <center><div id="htmlElement" style="background: rgba(255,255,255,0.01); color: #333; font-size: 48px; width:100%"></div></center>
    </div>

このHTML表示情報をAR空間上のPLANEに張り付けています。

次は、コメントの設定とPLANEの生成

index.htmlのL133-143

    // コメント入力したら描画した線の終端に反映する。
    function inputComment() {
      var htmlElement = document.querySelector("#htmlElement");
      htmlElement.innerHTML = document.querySelector("#commentText").value;

      plane = document.createElement("a-plane");
      plane.setAttribute("position", endPos);
      plane.setAttribute("scale", "0.2 0.1 0.1");
      plane.setAttribute("material","shader: html; target: #htmlElement; opacity:0.99");
      plane.setAttribute("look-at", "[camera]");
      scene.appendChild(plane);
    }

テキストボックスのコメントを上記描画用のHTMLソースに追加。html-shaderでPLANEに張り付けています。加えて、look-atで常にカメラの方向を向くようにしています。

まとめ

細かいところは飛ばしていますが、「どんなことをしたくて、どんな実装をしているか」はなんとなくわかっていただけたのではないでしょうか。
動画をとってアップロードする機能まではできませんでしたが、アイデア出しから始まって、初めて触るARCoreの挙動に戸惑いながら、約10時間ほどでここまで作れたのは自分でもびっくりしています。

MercariARハッカソンの参加者方々の作品を拝見して回り感じたこととして、音声認識と位置共有は今後ARを使っていく上で必須機能かなぁと感じました。あと、実装をUnityに乗り換えようかと本気で迷いました。実装楽そうだし、アセットとか、サードパーティライブラリとかがそろってて、成果物の完成度が段違いですもん。超嫉妬w
ARCore機が手元にないのでまたWebVRに戻りますが、機会があればWebARでアイデア練って一週間くらい本気で作りこんで見たいです。

終わり