あのアートの作り方 跳ね返り編 (Processing)

# はじめに

これは「[あのアートの作り方](https://qiita.com/Snowman-s/items/c992312450a3884c2542)」の続編ですが、読んでなくても良いように書いている...はず。

この記事は「Processingやってみたくて、インストールしてみたはいいけどどういう風に作ればいいか分からない」という方のために書きました。
チュートリアルを通して、作品の基本となるコードや、(ゆるい)数学的な感覚を掴んでもらえると嬉しいなというものです。

つまりはProcessingのインストールと、基本的な文法の説明を割愛します。それでは行ってみましょう。

**めちゃくちゃGIF画像を使っているので気を付けてください。通信量的に。**

# 今回作ってみる作品
今回は (再び私の) この作品を作ってみましょう。

https://twitter.com/SnowEsamosc/status/1459753224668286980

上のコードは[p5.js](https://p5js.org/)というProcessingのJavaScript版で書かれています。
これをProcessingのJava版に移植し、分かりやすくしたものが以下になります。

```java
int frame=9000;
void setup() {
  size(500, 500);
}
void draw() {
  clear();
  noStroke();
  for (float y=0; y<600; y+=30) {
    for (int n=1; n<80; n++) {
      float angle = (frame+n)*noise(y)/40.0;
      float x = acos(cos(angle))/PI*500;
      float deltaY;
      if (sin(angle)<0) {
        deltaY = 50*sin(x/100*PI);
      } else {
        deltaY = 0;
      }
      circle(x, y+deltaY, 5);
    }
  }
  frame++;
}
```

# キャンバスの作成 (再掲)
まず、これから作品を描くためのキャンバスを作ります。そのために、以下のコードを書きます。

```java
void setup() {
  size(500, 500);
}

void draw() {
  clear();
}
```

実行結果：
![out.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496507/361c2ac0-f7f7-7156-a060-c6fedc801433.png)


これは、動きのあるスケッチを作るときにはほぼお決まりの文句なので、すらすらかけるようになっておくといいでしょう。
なお、size()に関しては好きな値でいいですが、ここでは 500x500 とします。

# フレーム数を数える
これから、時間経過によって動きを変えるようにしたいので、そのために、時間をカウントする変数を追加します。

```java
int frame=0; //<--追加

void setup() {
  size(500, 500);
}

void draw() {
  clear();
  
  noStroke();
    
  frame++; //<--追加
}
```

実行結果：変化なし

変数`frame`の内容は0から始まり、draw()関数が終了するたびに`frame`がひとつずつ増加していきます。結果として、`frame`には現在何フレームが経過したかの値が入ることになります。

> もしかしたら、あなたは「[frameCount](https://p5js.org/reference/#/p5/frameCount) でいいやんけ！」と思ったかもしれません。しかし、このように自分で変数を用意すると後で変更が容易になります。例えば「キーを押している間だけスケッチの速度が２倍になる」などの処理が簡単に実装できますし、実際、今回もそういう感じの変更を行います。

# 右に動く円を召喚する (x = frame)
まず、あの作品を作るには動く円を書くことが必要です。

```java
int frame=0;

void setup() {
  size(500, 500);
}

void draw() {
  clear();
  
  noStroke();

  //x座標を少しずつずらす
  float x=frame;
  circle(x, 250, 5);
  
  frame++;
}
```

実行結果：
![hanekaeritmp.mov.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496507/d89b3ef6-08a1-7cde-ebe8-8b4d8c530eca.gif)

このようにx座標をframeに応じて増やすことによって、「経過したフレーム数が大きくなるほど大きいx座標に」円を書くことができます。

ところで、これは見方を変えると、x = frameという関数によってx座標が決まっているともいうことができますね？グラフにしてみるとこんな感じです：

![out.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496507/c3603059-ae80-b3e7-e747-d458a6420376.png)

# 円を行ったり来たりするようにする (x = acos(cos(frame)))

これに関してはまず見てもらった方が早そうな。

```java
int frame=0;

void setup() {
  size(500, 500);
}

void draw() {
  clear();
  
  noStroke();

  //x座標を左右に行ったり来たりさせる
  float angle = frame/40.0;
  float x=acos(cos(angle))/PI*500;
  circle(x, 250, 5);
  
  frame++;
}
```

実行結果：
![waves_circle_image.mov.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496507/dbdc2293-f798-aca1-acd7-ceb0d00e4eb2.gif)


実際は円を左右に行ったり来たりさせたいので、上下にカクカク動く関数、いわゆる**三角波**が必要です。
x = **acos(cos(frame))**という関数を用いるとそれを実現することができます。
![out.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496507/f2cc003b-d2a1-a44a-466a-bfb97284d6e8.png)

ただ、このままでは変化が速すぎ、かつ、小さすぎなので、それを調整してやる必要があります。
つまり、周期と振幅を調整する、ということですね。[前回の記事](https://qiita.com/Snowman-s/items/c992312450a3884c2542#%E7%B7%9A%E3%82%92%E6%9B%B2%E3%81%92%E3%82%8B) が参考になるかもしれません。

いい感じの調整を施したのが以下になります。(目盛りが変わっていることに注目)
> 実際は「書いて動かす」を繰り返すことによっていい感じの数値を探しました。

![out.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496507/3fa1fb68-dde3-c746-3799-91c2ba04c9f4.png)

これを `circle()` のx座標に対応させることで円を左右に行ったり来たりさせることができます。
ちょっと複雑なので、 `cos()` の引数を `angle` として外に出しておきました。

# 円を並べて線にする
小さい円をたくさん並べると一つの線のようになります。([参考：前回の記事](https://qiita.com/Snowman-s/items/c992312450a3884c2542#%E5%86%86%E3%81%A8%E7%B7%9A))
円が一つだけでは少し寂しいので、1フレームずつずれた円を書いていくことによって"少しだけずれた"円をつなげて、一つの線にしたいと思います。

```java
int frame=0;

void setup() {
  size(500, 500);
}

void draw() {
  clear();
  
  noStroke();

  for(int n=1;n<80;n++){
    //frameのかわりに "frame+n" を使う
    float angle = (frame+n)/40.0;
    float x=acos(cos(angle))/PI*500;
    circle(x, 250, 5);
  }
  
  frame++;
}
```

実行結果：
![waves_circle_image.mov (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496507/66662400-ea92-5b49-141c-fe0beea784f2.gif)

`frame` を `frame + n` に置き換えました。こうすることで、フレーム数が `n` だけずれた時間の円を書くことができるようになります。そして、`n`をfor文でたくさん用意すれば、その数の円がつながっていくのです！

> 例えばn=1の時は (n=0の時と比べて) 1フレームだけ後、n=2なら2フレーム後、っていう様に１フレームずつずれていきます。

このようにたくさん円をつなげることで、一つの線になっているように見えるのです。

# 線を曲げる
ここであなたは何故か線を曲げたくなる衝動に駆られました。では、線をぐにゃぐにゃに曲げてみましょう。

```java
int frame=0;

void setup() {
  size(500, 500);
}

void draw() {
  clear();
  
  noStroke();

  for(int n=1;n<80;n++){
    float angle = (frame+n)/40.0;
    float x=acos(cos(angle))/PI*500;
    //yの変化する分
    float deltaY = 50*sin(x/100*PI);
    circle(x, 250 + deltaY, 5);
  }
  
  frame++;
}
```
実行結果：
![waves_circle_image.mov (2).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496507/208ad5f0-224a-a8fd-4411-2580cb39aed7.gif)


まず、`deltaY`という新しい変数を用意して、元々のy座標からいくらずらすか、を入れておきます。ここでは、`50*sin(x/100*PI)`という式を用意しました。これは、`sin(x)` に"いい感じの調整"をしたものになります。これをもちいると、xの値に応じて線を滑らかに曲げる、ということができます。 ([参考：前回の記事](https://qiita.com/Snowman-s/items/c992312450a3884c2542#%E7%B7%9A%E3%82%92%E6%9B%B2%E3%81%92%E3%82%8B))

これと元々のy座標を足したものをcircleの新しいy座標とすることによって、この波を生み出すことができます。

> 補足：後の為に、`x=0` か `x=500`であるような点では`deltaY=0`でなければなりません。そうしなければ端で中心からずれてしまい、折り返しの際に円の"瞬間移動"が起きてしまいます。変えて遊んでみるときは注意です。

# 折り返しの時は線を真っすぐにする
あなたは変化を幾分かつけるために、線が折り返してきたときに真っすぐにしていきたいと思いました：

```java
int frame=0;

void setup() {
  size(500, 500);
}

void draw() {
  clear();
  
  noStroke();

  for(int n=1;n<80;n++){
    float angle = (frame+n)/40.0;
    float x=acos(cos(angle))/PI*500;
    float deltaY;
    //sin(angle)によってdeltaYを変える
    if (sin(angle)<0) {
      //ぐねぐねモード
      deltaY = 50*sin(x/100*PI);
    } else {
      //直線モード
      deltaY = 0;
    }
    circle(x, 250 + deltaY, 5);
  }
  
  frame++;
}
```

実行結果：
![waves_circle_image.mov (3).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496507/c56ea57d-03dc-9168-1b21-b7228f0fc957.gif)

`deltaY`が0のとき、基準のY座標からのずれがずっと0になるので、結果的に、(折り返し中の)全ての円が同じY座標を持つため、真っすぐの線になります。(「円を行ったり来たりするようにする」の章でのコードが、まさに`deltaY=0`の状態です。)

ですから、`x`が増えているか減っているかどうかによって`deltaY=0`と、`deltaY = 50*sin(x/100*PI)`をいい感じに切り替えていけば、動きが変えられます。

では、 **xが増えている・減っている、どちらなのか。**どう知ればいいのでしょうか？
たぶん`x` の動きは `cos(angle)`と似てるんじゃないかなーと思って調べると：

> ![out.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496507/33f2585f-f99c-0f26-e4af-533b9cbc0eac.png)
> ![out.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496507/6f5dc3c2-ee6c-bd33-8724-6a4302302ad9.png)

**「cos(angle) が増加するならacos(cos(angle))が減少する。」**

よって、単純に`cos(angle)`の増加・減少を考えれば、`x`の増加・減少を知ることができます。

・・・で、
`cos(angle)`の増加・減少は**微分によって**知ることができ、`cos(angle)`の微分はおおよそ`sin(angle)`なので、それが0より大きいか、小さいかによって分岐させればよいことになります。その判定をしている部分が`if (sin(angle)<0)`なのです。

少々難しいですが、参考までに`acos(cos(angle))`と`cos(angle)`と`sin(angle)`のグラフを貼っておきます。
`sin(angle)<0`の部分に着色しておきました。

![out.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496507/b01f9bc6-b73f-b163-c8cd-63462db988ba.png)
![out.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496507/f31b525d-1d0b-3719-743d-f426c31966fb.png)
![out.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496507/eb06c742-43d6-99dd-87c8-9f0399bd0f48.png)

`acos(cos(angle))`、つまり`x`が減少傾向の時に、`sin(angle)`は0より小さくなる、ということが分かってもらえたでしょうか。

# 線を増やす
これまでで、おおよそのギミックは終わりましたが、線が一本じゃさみしいので増やしていきたいですよね。

```java
int frame=0;

void setup() {
  size(500, 500);
}

void draw() {
  clear();
  
  noStroke();

  //基準のy座標をたくさん用意する
  for (float y=0; y<600; y+=30) {
    for(int n=1;n<80;n++){
      float angle = (frame+n)/40.0;
      float x=acos(cos(angle))/PI*500;
      float deltaY;
      if (sin(angle)<0) {
        deltaY = 50*sin(x/100*PI);
      } else {
        deltaY = 0;
      }
      //250をyに変更
      circle(x, y + deltaY, 5);
    }
  }
  
  frame++;
}
```

実行結果：
![waves_circle_image.mov.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496507/18ab0da9-f956-1fcb-3e0c-79b2c651a0a2.gif)

今までは`y=250`を基準としていましたが、その基準をたくさん用意することで線を並べることが出来ました。

# y座標ごとに線の進行速度を変える
全部同じ動きの線ではつまらないので速度を変えます。

```java
int frame=0;

void setup() {
  size(500, 500);
}

void draw() {
  clear();
  
  noStroke();

  for (float y=0; y<600; y+=30) {
    for(int n=1;n<80;n++){
      //基準のy座標によって速度を変える
      float angle = (frame+n)*noise(y)/40.0;
      float x=acos(cos(angle))/PI*500;
      float deltaY;
      if (sin(angle)<0) {
        deltaY = 50*sin(x/100*PI);
      } else {
        deltaY = 0;
      }
      circle(x, y + deltaY, 5);
    }
  }
  
  frame++;
}
```

実行結果：

[noise関数](https://p5js.org/reference/#/p5/noise)はその引数によって異なる値を返します。 ([参考：前回の記事](https://qiita.com/Snowman-s/items/c992312450a3884c2542#%E5%86%86%E3%81%AE%E5%9B%9E%E8%BB%A2%E9%80%9F%E5%BA%A6%E3%82%92%E3%81%9A%E3%82%89%E3%81%99%E5%B0%91%E3%81%97%E9%87%8D%E8%A6%81))

ですから、`(frame+n)`を、`(frame+n)*noise(y)`に置き換えることで、`angle`の増加する速さが`y`によって変化します。結果的に、`x`の変化する速さが`y`によって変わることになります。

# 開始時刻を八百長する (ほとんど再掲)
速度は変えることができましたが、開始時点では結局全部同じ位置からスタートしてあまり美しくないので、バラバラの位置から開始するようにします。

```java
//時刻ずらし
int frame=9000;
void setup() {
  size(500, 500);
}
void draw() {
  clear();
  noStroke();
  for (float y=0; y<600; y+=30) {
    for (int n=1; n<80; n++) {
      float angle = (frame+n)*noise(y)/40.0;
      float x = acos(cos(angle))/PI*500;
      float deltaY;
      if (sin(angle)<0) {
        deltaY = 50*sin(x/100*PI);
      } else {
        deltaY = 0;
      }
      circle(x, y+deltaY, 5);
    }
  }
  frame++;
}
```
実行結果：
![waves_circle_image.mov_1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496507/2df47d8b-8834-86c3-5640-92fd97ab1405.gif)

位置をずらすといっても、速度が既にバラバラなので、十分な時間がたった後なら、開いている場所もバラバラであるといえますね。
よって、スケッチを動かす前に、十分な時間を予め経過したものにします。
このためには単に経過時間を表すframeに9000を先に入れておきます。

こうすれば9000フレーム経過した地点から回転が始まるのでさすがにバラバラになっていますね。

**これにて完成です！**

# あとがき
もしかしたらやっぱり前の記事を読んでいないと理解が難しいかもしれない(ぉ

この記事を読んで下さりありがとうございました！