【サンプルコード付き】p5.jsでマウスに反応していい感じにフワフワする波を書く

## これは何

昔p5.jsでつくったマウスに追従する波の書き方とコードをまとめたものです。
備忘録もかねてめちゃめちゃ細かく書いてます。
自分が書いたコードなのに全然内容が思い出せなくて焦ったのでちゃんとコメントつけたり分かりやすい変数名使うの大事だなと思いました。

## 作ったもの

### 最終成果物

![01.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577501/a2cbb1ac-7f79-815b-97ae-b3dabce39377.gif)

[実際の動きはこちらから確認できます](https://editor.p5js.org/aizawa213/full/ZfkDPDF7l)

マウスホバーするとマウスに追従して波がいい感じに動きます。

## やったこと解説

やってることの手順はこんな感じです

1. ふわふわ動く波を描画する
1. マウスの位置を頂点とするガウス曲線を擬似的に描く
1. ふわふわ動く波が``2``のガウス曲線にゆるやかに近づいていくようにする

それぞれ解説していきます。

### 1. ふわふわ動く波を描画する

まずはふわふわ動く波を描画します。

#### 点を繋げて曲線を書く

まず、x座標を動かしながら点を打っていくことで線を描画します。今回の場合は``40``ずつ右に移動しながら点を打っています。
「え、刻み方荒くない？」って思いますよね。（私もコード読み返しながら思いました。）

確かに40ずつ刻んで点を打つだけではとても線には見えないのですが
``beginShape();``と``endShape(CLOSE);``でコードを囲ってあげると点どうしをつながった線分を書いてくれるので、この粗さでもちゃんと線が描画されます。
さらに、頂点の座標を``curveVertex()``で指定することで点どうしを曲線でつないでくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577501/c8677b48-40a0-cad8-fa81-bc892e326824.png)

同じy座標に点を打っていくだけでは描画されるのはただの直線なので、点を打つごとにy座標の位置をいい感じに変えてあげる必要があります。

こういう「いい感じにランダムなもの」を生成したい時に便利なのがパーリンノイズです。パーリンノイズは本当にスゴイやつです。

ランダム関数が指定された範囲内で完全にランダムな数値を出すのに対して、パーリンノイズは渡した引数の変化に応じて「なんかいい感じにランダムな数値」を返してくれます。

言葉で説明してもはて:thinking:という感じなので、ランダム関数を使って生成した波とパーリンノイズを使って生成した波を見比べてみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577501/1a3d8033-2e6f-4169-7b11-cdd53927faa7.png)

上の赤いのがパーリンノイズを使って描画した曲線、下の青いのがランダム関数を使って描画した曲線です。パーリンノイズで描画した曲線のほうが変化がなだらかで自然な感じになっているのが分かると思います。

[サンプル+コードはこちら](https://editor.p5js.org/aizawa213/sketches/z3CzwlDo0)

ではパーリンノイズを使って波を描いていきましょう。

- パーリンノイズ基礎知識
    - ``noise()``という関数を使うと、引数に応じて0~1の間の数値を返してくれる
    - 引数を少しずつ変化させると、返してくれる数値もなだらかに変化する
    - 引数と返ってくる値は1:1で対応している
    - 引数の変化は小さいほど返ってくる値もなだらかになる

今回はxoffという変数を引数にして、点を打つごとに0.05ずつ数値を増加させていきました。

``noise()``で帰ってくる値は0~1の間の数値なのでさらに``map()``を使って波を描画するのに丁度よい範囲にマッピングします。
今回は``height * 1/10, height * 8/10``の範囲にリマッピングしました。

ここまでをコードにするとこんな感じです。
[サンプル+コードはこちら](https://editor.p5js.org/aizawa213/sketches/ZeHrtGnoa)
良い感じになるように[noiseDetail](http://www.musashinodenpa.com/p5/index.php?pos=506)を使用しています。

```javascript
var canvas;

function setup() {
  canvas = createCanvas(window.innerWidth, window.innerHeight);
  noiseDetail(2,0.2);
}

function draw() {
  background(255,255,255);
  noStroke();
  beginShape();
  fill(182, 208, 220, 70);
  //波を描画する
  var xoff = 0;
  for (var x = 0; x <= width+200; x += 40) {
    // xoffを引数にしてnoise()で値を取得し扱いやすい範囲にリマッピング
    var y = map(noise(xoff), 0, 1, height * 1/10, height * 8/10);
    // 曲線の頂点座標を指定する
    curveVertex(x, y);
    // x
    xoff += 0.05;
  }
  curveVertex(width, height);
  curveVertex(0, height);
  endShape(CLOSE);
}
}
```

##### Ref

[noise | p5.js公式リファレンス](https://p5js.org/reference/#/p5/noise)
[map | p5.js公式リファレンス](https://p5js.org/reference/#/p5/map)

#### パーリンノイズを使って波をフワフワさせる

先ほどまででこんな感じの波を描画できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577501/8f26422c-7bd4-abe9-6668-73f833f15e67.png)

、、、これを波と認識するかはかなり個人差がありそうです。
「波っぽさ」を出すために動きを追加していきます。

これは簡単で、実は``noise()``は引数を複数指定できるので、先程の曲線の頂点を指定していた``var y = map(noise(xoff), 0, 1, height * 1/10, height * 8/10);``の``noise(xoff)``の部分に変化し続ける値を引数として追加してあげれば良いのです。

今回はこんな感じでyoffという引数を追加しています。

```javascript
var y = map(noise(xoff,yoff), 0, 1, height * 1/10, height * 8/10);
```

[サンプル+コードはこちら](https://editor.p5js.org/aizawa213/sketches/O42K9mnzJ)

プレビューを確認してみるとこんな感じです。
![wave.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577501/3ce23087-94c2-dfe7-c711-3377852098af.gif)

これは！もう大分波です。これはかなりの人が「波だな」と思ってくれるのではないでしょうか。良い感じです。

### 2. マウスの位置を頂点とするガウス曲線を擬似的に描く

若干もう波描けたし良くないか？という気持ちになってきましたが、本題の「マウスに反応していい感じにフワフワする」部分を作っていきます。
ここが当時かなり難航した部分で何パターンもやり方を試しては「思った通りの動きにならん、、」と苦悩した部分なのですがさっぱり覚えてないので最終形だけ解説します。

結論から言うと、A [マウスの位置を頂点とするガウス曲線] を想定し、B [描画した波の形] に足してあげます。

図にするとこんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577501/386cc70f-ac62-71bc-fcf1-9ff585d232da.png)

ガウス曲線は数学の授業でよくみる素敵な曲線のアレです。
![gaussian.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577501/17db8ec6-931a-5ef7-a501-2aadfc50b065.png)
[ガウス関数](https://www.eng.kagawa-u.ac.jp/~tishii/Lab/Etc/gauss.html)

では下準備として、マウスの位置を頂点とするガウス曲線を描いていきます。

ガウス曲線のことはよくわかりませんが数式をそのままドーンしてx座標を入れるとy座標を返してくれる関数を作ります。
平均0、標準偏差1のガウス曲線が気に入ったし数値的にも扱いやすそうなのであまりよく考えずそいつを使います。

![325px-Normal_Distribution_PDF.svg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577501/538a7938-1a18-e86f-1d89-e7053c175f7d.png)
[正規分布](https://www.wikiwand.com/ja/%E6%AD%A3%E8%A6%8F%E5%88%86%E5%B8%83)

```javascript
function Gaussian(x){
  return 1/(pow(2*PI,1/2)) * Math.exp(- pow(x,2)/2)
}
```

数式の意味はよく分かっていないけど、x座標の値を入れればy座標の位置を返してくれる関数が出来上がりました。

試しに``x=2/width,y=0``が頂点となるガウス曲線を描いてみます。
コードはこんな感じ

```javascript
fill(255, 0, 0);
for (var i = 0; i <= width; i += 10) {
  var gaussianX = map(i, 0, width, -5,5);
  var gaussianY = map(Gaussian(gaussianX,1), 0, 0.5, height, 0);
  ellipse(i, gaussianY, 5);
}
```

平均0、標準偏差1のガウス曲線は最大値が0.4くらいらしい（正確には0.3989422804らしいです）ので、``map(Gaussian(gaussianX,1), 0, 0.4, height, 0);``の部分で0~0.4の範囲を0~画面の高さの範囲にリマッピングしていい感じの波になるようにしています。
ついでにp5.jsのキャンバスは左上が0なので見た目的に上の方に頂点が来るよう逆さまにマッピングしています。

また、平均0、標準偏差1のガウス曲線はx座標が-5~5の間に良い感じの波が存在するので、こちらも``gaussianX = map(i, 0, width, -5, 5);``でx座標の位置を関数に渡すときに-5~5の範囲にリマッピングしています。

[サンプル+コードはこちら](https://editor.p5js.org/aizawa213/sketches/Rei3a_Ska)

確認してみると良い感じのガウス曲線が書けています。:relieved:
![editor.p5js.org_aizawa213_full_Rei3a_Ska.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577501/1f181a67-ff7d-322a-dd1b-ddb84350c8b8.png)

次はこのガウス曲線をマウスの位置が頂点になるようにいじって行きます。

高さに関しては先程の``map(Gaussian(gaussianX,1), 0, 0.4, height, 0);``のマッピングで画面の高さを指定していた部分を``mouseY``に置き換えてあげれば良さそうです。

X座標に関しては、``i = mouseX``の時に``gaussianX = 0``（≒y座標が最大になる）になれば良さそうです。

``i``と``mouseX``の距離を``distX``とすると、``i = mouseX``の時に``distX = 0``となるので扱いやすそうです。

distXは-widthからwidthの範囲に分布しているのでマッピングの範囲も修正してあげます。

コードにするとこんな感じです。

```javascript
fill(255, 0, 0);
for (var i = 0; i <= width; i += 10) {
  var distX = abs(mouseX - i);
  var gaussianX = map(distX, -width, width, -5,5);
  var gaussianY = map(Gaussian(gaussianX,1), 0, 0.4, height, mouseY);
  ellipse(i, gaussianY, 5);
}
```

無事ガウス曲線がマウスに追従してくれるようになりました。

![gaussian.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577501/54e92b4d-6f98-1476-2566-e0a17244e307.gif)

[サンプル+コードはこちら](https://editor.p5js.org/aizawa213/sketches/blRCPBjM2)

### 3. ふわふわ動く波が``2``のガウス曲線にゆるやかに近づいていくようにする

これで波がマウスに反応するようになります。
ここまで長かった、、、、

先程作ったマウスの位置が頂点になるガウス曲線を縮めて最初に作った緩やかに動く波に乗せる形で実現します。

流石にあのまま元の波に反映させると数値が大きすぎるのでちょうど良いサイズになるようマッピングします。

試しにこんな感じに指定してみたら良い感じでした。

```javascript
var gaussianY = map(Gaussian(gaussianX), 0, 0.4, mouseY/4, 0);
```

更にこの``gaussianY``を曲線のy座標に加算して反映させます。

```javascript
curveVertex(x, y + gaussianY);
```

なかなか良い感じです。

![movingwave.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577501/da89df17-c225-7c16-7402-c4dddc0581ae.gif)


[サンプル＋コードはこちら](https://editor.p5js.org/aizawa213/sketches/IgWllXiId)

実際の制作物ではここからマウスとの距離に応じてちょっと調整を入れたり高さを変えた波を複製して表現をリッチにしたりしているのですが細かい上に長くなりすぎるので割愛します。

これでマウスに反応していい感じにフワフワする波が描けました！お疲れ様でした！！