A-Frame＋AR.jsでマーカー検出と同時にアニメーションを再生する方法

#概要
A-FrameとAR.jsによるWebARにおいて、マーカーの検出と同時にアニメーションを再生する必要があったため、その備忘録です。
`AFRAME.registerComponent`でマーカの検出/非検出をイベントとして登録し、そのイベントが呼ばれたタイミングでアニメーションを再生/停止します。
動画はワイヤーフレームのboxがHiroマーカーから5秒かけて上昇するアニメーションの繰り返しですが、マーカーを検出したタイミングでアニメーションが初期位置から再生されています。
![animation_with_marker.gif](https://qiita-image-store.s3.amazonaws.com/0/265206/fc912f5c-7fd9-c276-bb8a-d2f9b6088602.gif)

#環境
A-Frame -> 0.8.0
AR.js -> 1.6.2

#a-animation
回転や移動等の簡単なアニメーションは、A-frameの`a-animation`タグで実現できます。
下記の例では、ワイヤーフレームのboxがHiroマーカーから5秒かけて上昇するアニメーションが繰り返し再生されます。

```html:box_up.html
<!doctype HTML>
<html>
<script src="https://aframe.io/releases/0.8.0/aframe.min.js"></script>
<script src="https://cdn.rawgit.com/jeromeetienne/AR.js/1.6.2/aframe/build/aframe-ar.js"> </script>

<body style="margin : 0px; overflow: hidden;">
    <a-scene embedded arjs>
        <a-marker preset="hiro">
            <a-box position="0 0.5 0" wireframe="true">
                <a-animation attribute="position" from="0 0.5 0" to="0 2.5 0" dur="5000" repeat="indefinite">
                </a-animation>
            </a-box>
        </a-marker>
        <a-entity camera></a-entity>
    </a-scene>
</body>

</html>
```

しかし、アニメーションはページの読み込みと同時に再生が開始されるため、boxが表示されていない状態でも位置は移動しています。
そのため、Hiroマーカーを検出したタイミングによって、最初にboxが表示される位置が異なってしまいます。
ループするアニメーションの場合はさほど問題にはなりませんが、アニメーションを最初から再生して表示したい場合などには適していません。

アニメーションを任意のタイミングで再生する方法として、`a-animation`の`begin`及び`end`属性があります。
下記に示す[公式の例](https://aframe.io/docs/0.8.0/core/animations.html#begin)では、アニメーションを開始するイベントとして、`begin`属性に`'fade'`というイベント名を指定しています。
再生にはアニメーションが設定されている親要素（ここでは`id="fading-cube"`を持つ`a-entity`）を取得し、`emit('fade')`と指定されたイベントを発火することでアニメーションを開始することができます。

```html:begin.html
<a-entity id="fading-cube" geometry="primitive: box" material="opacity: 1">
  <a-animation attribute="material.opacity" begin="fade" to="0"></a-animation>
</a-entity>
```
```javascript:begin.js
document.querySelector('#fading-cube').emit('fade');
```

したがって、「マーカーを検出したタイミング」を取得してイベントを発火する事で、マーカーの検出と同時にアニメーションを再生する事ができます。

#マーカーを検出してアニメーションを再生
マーカーの検出/非検出をイベントとしてトリガーする方法はAR.jsに用意されており、[こちら](https://github.com/jeromeetienne/AR.js/blob/master/aframe/examples/marker-events.html)に実装例があります。

これらを組み合わせて、マーカーの検出と同時にアニメーションを再生し、マーカーを見失うとアニメーションを停止する機能を実装しました。
マーカーを再検出した場合には、アニメーションは再度頭から再生されます。

```html:marker_trigger_animation.html
<!doctype HTML>
<html>
<script src="https://aframe.io/releases/0.8.0/aframe.min.js"></script>
<script src="https://cdn.rawgit.com/jeromeetienne/AR.js/1.6.2/aframe/build/aframe-ar.js"> </script>
<script>
    AFRAME.registerComponent('registerevents', {
        init: function () {
            var marker = this.el;

            // マーカーを検出したイベントの登録
            marker.addEventListener('markerFound', function () {
                var markerId = marker.id;
                console.log('markerFound', markerId);

                // アニメーションの開始
                document.querySelector('#box').emit('markerfound');
            });

            // マーカーを見失ったイベントの登録
            marker.addEventListener('markerLost', function () {
                var markerId = marker.id;
                console.log('markerLost', markerId);

                // アニメーションの停止
                document.querySelector('#box').emit('markerlost');
            });
        }
    });
</script>

<body style="margin : 0px; overflow: hidden;">
    <a-scene embedded arjs>
        <a-marker preset="hiro" id="marker-hiro" registerevents>
            <a-box id="box" position="0 0.5 0" wireframe="true">
                <a-animation attribute="position" from="0 0.5 0" to="0 2.5 0" dur="5000" repeat="indefinite" begin="markerfound" end="markerlost">
                </a-animation>
            </a-box>
        </a-marker>
        <a-entity camera></a-entity>
    </a-scene>
</body>

</html>
```
[こちら](https://arara-kamasaka.github.io/animation-with-marker)から実行する事ができます。

対象のマーカーの`a-marker`タグに`registerevents`属性を追加する事で、マーカーの検出/非検出のタイミングをイベントとして登録する事ができます。
今回はマーカーを検出したイベント`markerFound`をトリガーに、アニメーションを再生するイベント`'markerFound'`を、マーカーを見失ったイベント`markerLost`をトリガーに、アニメーションを停止するイベント`'markerlost'`を発火する事で、マーカーの検出/非検出に応じてアニメーションを再生/停止しています。

#まとめ
AR.jsのマーカーの検出イベントと、`a-animation`の`begin`/`end`属性を組み合わせる事で、マーカーの検出/非検出をトリガーにアニメーションの再生/停止を制御する事ができました。
この他にも、マーカーを検出して音声を再生したり、javascriptの実行結果によって特定のアニメーションを再生するなどの応用ができると思います。

#参考
https://github.com/jeromeetienne/AR.js/blob/master/aframe/examples/marker-events.html
https://aframe.io/docs/0.8.0/core/animations.html
https://jsfiddle.net/donmccurdy/tzm4q9n7/