AR.js Marker Based にピンチ操作を追加

# 概要

スマートフォンでARマーカーを読み取らせて表示したオブジェクトをピンチ操作で拡縮したい、という需要があったのでチャレンジ。うまくいきました。

# 実装手順

## 豆腐を出すだけのシンプルなコード

```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>A-Frame AR with pinch scale</title>
    <script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>
    <script src="https://raw.githack.com/jeromeetienne/AR.js/2.0.5/aframe/build/aframe-ar.js"></script>
  </head>
  <body>
    <a-scene embedded arjs>
      <a-marker preset="hiro">
        <a-box id="cube" material="opacity: 0.75;"></a-box>
      </a-marker>
      <a-entity camera></a-entity>
    </a-scene>
  </body>
</html>
```

hiroマーカーで半透明の豆腐が出ました。

![aa.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579071/8ea1afb4-d4e7-6060-dcb9-f07162f0e66d.png)

## ピンチ操作で拡縮できるように

a-scene に独自のアトリビュート scale-controls を追加。

```html
<a-scene embedded arjs scale-controls>
```

scale-controls を A-Frame に registerComponent して中身を書く（この例では cube という id の要素に対する拡縮操作にしています）。

```js
<script>
      AFRAME.registerComponent("scale-controls", {
        init: function() {
          this.modelPivotEl = document.getElementById("cube");

          this.onTouchStart = this.onTouchStart.bind(this);
          this.onTouchMove = this.onTouchMove.bind(this);

          document.addEventListener("touchstart", this.onTouchStart);
          document.addEventListener("touchmove", this.onTouchMove);
        },

        onTouchStart: function(evt) {
          if (evt.touches.length === 3) {
            this.onTripleTouchStart(evt);
          }
        },

        onTouchMove: function(evt) {
          if (evt.touches.length === 2) {
            this.onPinchMove(evt);
          }
        },

        onTripleTouchStart: function(evt) {
          // reset
          var modelPivotEl = this.modelPivotEl;
          var modelScale = 1;
          modelPivotEl.object3D.scale.set(modelScale, modelScale, modelScale);
          this.modelScale = modelScale;
        },

        onPinchMove: function(evt) {
          // scaling
          var dX = evt.touches[0].clientX - evt.touches[1].clientX;
          var dY = evt.touches[0].clientY - evt.touches[1].clientY;
          var modelPivotEl = this.modelPivotEl;
          var distance = Math.sqrt(dX * dX + dY * dY);
          var oldDistance = this.oldDistance || distance;
          var distanceDifference = oldDistance - distance;
          var modelScale = this.modelScale || modelPivotEl.object3D.scale.x;

          modelScale -= distanceDifference / 50;
          modelScale = Math.min(Math.max(0.1, modelScale), 2.0);
          // Clamp scale.
          modelPivotEl.object3D.scale.set(modelScale, modelScale, modelScale);

          this.modelScale = modelScale;
          this.oldDistance = distance;
        }
      });
    </script>
```

# 実行結果

ピンチで拡縮、3点タッチでリセットできるようになりました。

![ezgif.com-gif-maker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579071/c039bfeb-88aa-48b4-083c-16f983d227a0.gif)

動作デモはこちら。スマホでQRコードから飛んで、下のHiroマーカーを写すと試せます。

![exported_qrcode_image_600.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579071/35bb5dfb-18ee-3724-873e-de54ab7f4b14.png)


A-Frame AR with pinch scale
https://yummy-rustic-shame.glitch.me

![hiro.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579071/432b0c8f-f44d-c077-6ff5-44a231a72f57.png)


# 参考

A-Frame 公式サンプルの model-viewer から必要なところだけいただきました。

aframe/examples/showcase/model-viewer at master · aframevr/aframe
https://github.com/aframevr/aframe/tree/master/examples/showcase/model-viewer