p5.js で Hammer.JS を使ったタッチ操作の実装：回転、ピンチイン／ピンチアウト（拡大／縮小）

p5.js でのタッチ操作、具体的には「回転、ピンチイン／ピンチアウト」の操作になるのですが、それらを [Hammer.JS](https://hammerjs.github.io/) を使って実装してみた、という話です。

最初に、今回実装したものを動作させた際の動画を掲載します。

https://twitter.com/youtoy/status/1627663028899688448

ここから実装をした話を書いていきます。

## 他の方が作られたサンプル
既に、Hammer.JS と p5.js を組み合わせて使われてる方がいないか、それらをキーワードにして検索すると、以下のページが検索でヒットします。

●p5.js Web Editor | hammer.js swipe
　https://editor.p5js.org/shiffman/sketches/HyEDRsPel

このサンプルは、Hammer.JS と p5.js を組み合わせてスワイプ操作を判定しているものでした。JavaScript のプログラムは以下の通りです。

```javascript
var msg = "swipe";

function setup() {
  createCanvas(windowWidth, windowHeight);

  textAlign(CENTER);
  textSize(30);
  noStroke();

  // set options to prevent default behaviors for swipe, pinch, etc
  var options = {
    preventDefault: true
  };

  // document.body registers gestures anywhere on the page
  var hammer = new Hammer(document.body, options);
  hammer.get('swipe').set({
    direction: Hammer.DIRECTION_ALL
  });

  hammer.on("swipe", swiped);
}

function draw() {
  background(250, 10, 100);
  text(msg, width / 2, height / 2);
}

function swiped(event) {
  console.log(event);
  if (event.direction == 4) {
    msg = "you swiped right";
  } else if (event.direction == 8) {
    msg = "you swiped up";
  } else if (event.direction == 16) {
    msg = "you swiped down";
  } else if (event.direction == 2) {
    msg = "you swiped left";
  }
}
```

上記の実装ではスワイプ操作のみを対象としていますが、内容的には簡単に「回転、ピンチイン／ピンチアウト」の操作にも流用できそうです。

## 回転を実装する
まずはスマホの画面上で、2本指で回転させる操作をした場合に、キャンバスを回転させるものを作ってみます。実装は、p5.js Web Editor を用いて行います。

以下に JavaScript のプログラムを掲載します。Hammer.JS を利用するために、p5.js Web Editor の index.html で、Hammer.JS を読み込む行を加えるのを忘れないようにしてください（※ 自分は CDN上のライブラリを読み込むようにしました）。

```javascript
let rStart, r, rEventStart;

function setup() {
  createCanvas(windowWidth, windowHeight);

  r = 0;

  const hammer = new Hammer(document.body, {
    preventDefault: true,
  });

  hammer.get("rotate").set({ enable: true });
  hammer.on("rotate", onRotate);
}

function onRotate(event) {
  if (event.eventType === Hammer.INPUT_START) {
    rStart = r;
    rEventStart = event.rotation;
  } else if (event.eventType === Hammer.INPUT_MOVE || Hammer.INPUT_END) {
    r = rStart + radians(event.rotation - rEventStart);
  }
}

function draw() {
  background(220);

  translate(width / 2, height / 2);
  rotate(r);
  line(-0.3 * width, 0, 0.3 * width, 0);
}
```

プログラムについて、少し補足します。

setup() の部分では、Hammer.JS の回転を利用するための処理を書いています。

また、draw() の中の処理は線を描画しており、その際、キャンバスの中心を原点として描画を回転させています。その回転する角度は、onRotate() の中で決めています。

そして、onRotate() の中では、回転の操作開始時と、その後の回転操作継続・終了時の処理を書いています。回転の操作開始時には、その時点でのキャンバスを回転させている角度と、タッチ操作で検出された角度を保存しています（`event.rotation` で得られる値は、マルチタッチした際のタッチした 2点がなす角度となるようです）。
その後の回転操作継続・終了時の処理では、最初に覚えた値から動いた角度分だけ、キャンバスを回転させるようにしています。なお、event.rotation は度数法（度）の値のようなので、弧度法（ラジアン）に変換して利用しています。

ここで用いた Hammer.JS のイベント（rotation）の内容については、[公式ページの API のページ](https://hammerjs.github.io/api/) を参照ください。
![公式ページの API のページ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/7552c33a-ca35-3811-65f8-43744710b745.png)

## 拡大／縮小を実装する
次は、拡大／縮小です。

以下に JavaScript のプログラムを掲載します。先ほどと同様に、Hammer.JS を利用するために、p5.js Web Editor の index.html で、Hammer.JS を読み込む行を加えるのを忘れないようにしてください。

```javascript
let s, sEnd;

function setup() {
  createCanvas(windowWidth, windowHeight);

  sEnd = 1;

  const hammer = new Hammer(document.body, {
    preventDefault: true,
  });

  hammer.get("pinch").set({ enable: true });
  hammer.on("pinch", onPinch);
}

function onPinch(event) {
  if (event.eventType === Hammer.INPUT_END) {
    sEnd = s;
  } else {
    s = sEnd * event.scale;

    if (s < 0.5) {
      s = 0.5;
    } else if (s > 3) {
      s = 3;
    }
  }
}

function draw() {
  background(220);

  translate(width / 2, height / 2);
  scale(s);

  rect(-50, -50, 100);
  line(-0.3 * width, 0, 0.3 * width, 0);
}
```

今回のプログラムでは、ピンチイン／ピンチアウトを行った際に、キャンバスで描画する図形の拡大／縮小を行うようにしています。

基本的に、ピンチ操作をしている間は、拡大／縮小率を描画にそのまま反映させています。そして、ピンチ操作が終了した際に、その際の拡大／縮小に用いた割合の値を、変数に保存しています（なおこの値は、最初は「1」で初期化しています）。