p5.js でのループアニメーションで「frameCount」と「剰余演算 or 三角関数」を使う ：2D・3D のそれぞれで x方向に往復する動きを例に ＋α

## 今回の内容
p5.js でループするアニメーションを作る際に、frameCount と剰余演算を組み合わせたものをよく使います。それに関連する話では以下の記事を書いていました。

●frameCount・剰余演算を使った p5.js でのループアニメーション：アニメーションの最初と最後に空白の時間を作る - Qiita
　https://qiita.com/youtoy/items/68f9dc64cb6cbe912bc4

そして今回は、「値の増加 ⇒ 値の減少 ⇒ 値の増加 ⇒ ...」という内容のループアニメーションを作る時の話です。それについて、「frameCount と 剰余演算」を組み合わせた方法について書きつつ、「frameCount と 三角関数」を組み合わせた方法も合わせて書いてみます。

## 2D
まずは 2D の描画で、横方向の位置座標を変化させる場合の例を書いてみます。

### 実装内容
最初に、例として実装した 3つの内容を掲載します。
他の実装方法もあると思いますが、試してみた内容のうち 3つをピックアップしてみました。

```javascript
function setup() {
  createCanvas(450, 350);

  fill(190, 160, 250);
  noStroke();
}

function draw() {
  background(30);

  const maxCount = 200;
  const f = frameCount % maxCount;

  // 例1
  const value =
    f < maxCount / 2
      ? map(f, 0, maxCount / 2, 0, 1)
      : map(f, maxCount / 2, maxCount, 1, 0);
  const x1 = width * value;
  circle(x1, 0.2 * height, 20);

  // 例2
  const f2 = f / (maxCount / 2);
  const x2 = width * min(f2, 2 - f2);
  circle(x2, 0.4 * height, 20);

  // 例3
  const x3 = (width * (1 + cos(frameCount / 30 + PI))) / 2;
  // const x3 = (width * (1 + sin(frameCount / 30 - HALF_PI))) / 2;
  circle(x3, 0.6 * height, 20);
}
```

### 処理内容の補足
上に掲載した 3つの方法について、それぞれを簡単に補足します。

例1・例2 は、以下の frameCount・剰余演算を使った処理で計算した f の値をもとにアニメーションさせます。

```javascript
  const maxCount = 200;
  const f = frameCount % maxCount;
```

#### 例１ について
例1 では、剰余演算で作られた 0 から　199 の値を前半分・後ろ半分に分けて、それらを　x方向の右移動・x方向の左移動にマッピングしています。マッピングには、 [p5.js の関数の map()](https://p5js.org/reference/#/p5/map) を使いました。
余談ですが、「map()」は今回の例に限らず、値の範囲を変換するのに重宝しています。

#### 例2 について
例2 では、剰余演算で作られた 0 から　199 の値を、0 から 1.99 までの値にして、さらにそれを処理した値を使います。最後に書いた処理について、具体的な処理は `min(f2, 2 - f2)` の部分です。
ここで、1 を超える値になったところで 2-f2 のほうが選択されるようになり、1 から 0 へ値が動く部分が作れます。

#### 例3 について
例3 は、frameCount を三角関数（ここでは cos()・sin()）の角度の部分で使っています。
どちらも、-1 から 1 の値をとるので、それが 0 から 1 の値になるような計算を入れています。

三角関数を使った場合、その前の 2つとは違って線形な変化ではなくなります。その違いがあるのはご留意ください。

### 別の書き方
map() を使った処理内容は、特定の範囲の値を 0 から 1 の範囲にするものです。
これは p5.js の [norm()](https://p5js.org/reference/#/p5/norm) を使って、以下のように書きかえることもできます。

```javascript: 書きかえ前
  const value =
    f < maxCount / 2
      ? map(f, 0, maxCount / 2, 0, 1)
      : map(f, maxCount / 2, maxCount, 1, 0);
```

```javascript: 書きかえ後
  const value =
    f < maxCount / 2
      ? norm(f, 0, maxCount / 2)
      : norm(f, maxCount, maxCount / 2);
```

また、以下の書き方でも OK です。

```javascript: 書きかえ後2
  const [lower, upper] =
    f < maxCount / 2 ? [0, maxCount / 2] : [maxCount, maxCount / 2];
  const value = norm(f, lower, upper);
```

## 3D
次は WEBGLモードを使った、3D描画の場合です。この場合、画面中央が原点になるため、その原点を起点にする形（x方向の画面中央を起点とした形）にしてみます。

```javascript
function setup() {
  createCanvas(450, 350, WEBGL);

  fill(190, 160, 250);
  noStroke();
}

function draw() {
  background(30);

  const maxCount = 200;
  const f = (maxCount / 4 + frameCount) % maxCount;

  // 例1
  const value1 =
    f < maxCount / 2
      ? map(f, 0, maxCount / 2, -1, 1)
      : map(f, maxCount / 2, maxCount, 1, -1);
  const x1 = 0.3 * width * value1;
  circle(x1, -0.2 * height, 20);

  // 例2
  const f2 = f / (maxCount / 2);
  const value2 = f2 < 1 ? 2 * f2 - 1 : -2 * f2 + 3;
  const x2 = 0.3 * width * value2;
  circle(x2, 0, 20);

  // 例3
  const x3 = 0.3 * width * cos(frameCount / 35 - HALF_PI);
  // const x3 = 0.3 * width * sin(frameCount / 30);
  circle(x3, 0.2 * height, 20);
}
```

基本的に、2D用に作ったものと同じような処理にしました。
画面中央の原点を起点にするための内容を加えています。

## 値の変化のスピードを変える
上で書いた例で、値の変化のスピードを変えたい場合もあります。

その場合は、 f の値を算出している部分の `frameCount` に、何らかの係数を乗算（または何らかの値で除算）してやれば OK です。もしくは、maxCount の値を変化させても良いです。

上の 3D のほうの 例1・例2 に関し、maxCount の値はそのままで変化のスピードを早くしたい場合は、例えば以下のようにします。

```javascript
  const f = (maxCount / 4 + frameCount * 3) % maxCount;
```

例3 についても同様です。上で書いたものでは、frameCount を 35 で除算していましたが、この除算する値を大きくしたり小さくしたりすることで、変化のスピードを変えられます（また、何らかの値を乗算しても良いです）。

```javascript
  const x3 = 0.3 * width * cos(frameCount / 35 - HALF_PI);
```

## おわりに
他にも p5.js でループアニメーションを作るやり方はありますが、今回は往復する動きを「frameCount と剰余演算を組み合わせたもの」か、または「frameCount と三角関数を組み合わせたもの」で作ってみました。

frameCount と rotate()・translate() を組み合わせて作る方法もありますが、それは、さらに他の要素も加えた内容にして、別の記事に書こうと思います。

### 【追記】 余談
最後に書いている「frameCount と rotate()・translate() の組み合わせ」という内容について、少しトリッキーな感じになっている気がしますが、コードを紹介してみます。

```javascript
let a = 0;
let b = 0.2;

function setup() {
  createCanvas(450, 350);

  fill(190, 160, 250);
  noStroke();
}

function draw() {
  background(30);

  const maxCount = 100;
  const f = frameCount % maxCount;

  if (f === 0) {
    a += PI;
    b = 1 - b;
  }
  const value = f / maxCount;
  const x1 = width * value;

  translate(width / 2, height / 2);
  rotate(a);
  translate(-width / 2, -height / 2 + b * height);
  circle(x1, 0, 20);
}
```