p5.js の 3D描画におけるカメラの位置変更を軽く試してみる（3つの異なるやり方にて）【その1】

p5.js の `createCanvas()` や `createGraphics()` で、以下の `WEBGL` を選択した場合の描画に関し、カメラの位置変更を行った場合の話に関する記事です。

●reference | WEBGL
　https://p5js.org/reference/#/p5/WEBGL

試した内容は以下のツイートにある通りで、詳細はこの後に説明をしていきます。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/p5js?src=hash&amp;ref_src=twsrc%5Etfw">#p5js</a> の 3D空間内のカメラ操作について、自分が理解していく第一歩として、簡単なサンプルを 3つほど作っての挙動の違いを比較してみました。<br><br>ちなみに、以下の 3パターン（細かな部分の調査・理解はこれから）。<br><br>■「camera()」のパラメータを変更<br>■「pan()・tilt（）」を利用<br>■「move()」を利用 <a href="https://t.co/unLD75IIGN">pic.twitter.com/unLD75IIGN</a></p>&mdash; you (@youtoy) <a href="https://twitter.com/youtoy/status/1452118867015180297?ref_src=twsrc%5Etfw">October 24, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 今回試したものに関する公式情報
[p5.js の reference](https://p5js.org/reference/)での「Lights, Camera」という項目の下に、以下のようなカメラ関連の内容が並んでいます。
![カメラ関連の内容.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/7c9c074d-dc01-c65a-9104-c7d95be91541.jpeg)

この中の「[camera() の項目](https://p5js.org/reference/#/p5/camera)」と「[p5.Camera の項目](https://p5js.org/reference/#/p5.Camera)」のページには、カメラ位置の変更に関わる処理がいくつか書かれています。

今回、それらの中の全てではないですが、気になるものいくつかを試してみました。　
　
### 試した処理の概要
今回試した具体的な処理は、冒頭に掲載したツイート本文にも書いた以下になります。

1. 「camera()」のパラメータ変更
2. 「pan()・tilt（）」の利用
3. 「move()」の利用

上記の 1つ目は、「[camera() の項目](https://p5js.org/reference/#/p5/camera)」で、以下のように書かれているものになります。
![カメラのパラメータ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/3d36cdb9-459e-5766-5085-c188c300d1ee.jpeg)

上記 2つ目と 3つ目は、「[p5.Camera の項目](https://p5js.org/reference/#/p5.Camera)」の Methods に含まれているものです。
![p5_Camera.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/024f4483-aa12-8b31-1e80-34a86ebf76cf.jpeg)

## お試し時に描画した内容
今回のカメラの位置変更を試す際、3D空間で描画してみたものは、以下の見た目のものになります。
![描画内容.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/b8e97456-ce7e-59da-af07-251969d03f5c.jpeg)

描画処理部分のソースコードを抜粋して書いておきます。

```javascript
  push();
  translate(-width / 4, -height / 4, 0);
  fill(0, 0, 255);
  box(80);
  pop();

  push();
  box(80);
  pop();

  push();
  translate(width / 4, height / 4, -100);
  fill(200, 100, 150);
  box(80);
  pop();
```

上記の2つ目の `box()` は色を指定していないですが、`setup()` の部分で `ambientLight(255, 255, 255)` を指定しているため、それに影響を受けた白い色になっています。

## カメラの位置変更の処理について（概要）
### カメラに関して試した処理： camera() のパラメータ変更
具体的なカメラの位置変更の処理内容は、以下となります。
以下の cameraX と cameraZ は、初期値がゼロで、キーの押下時に値が増減するようなものにしています。

```javascript
camera(cameraX + 0, 0, cameraZ + height / 2 / tan(PI / 6), 0, 0, 0, 0, 1, 0);
```

上記でゼロでない部分があるのは、公式リファレンス内でデフォルト値が以下となると説明されていたためです。

```javascript
camera(0, 0, height / 2 / tan(PI / 6), 0, 0, 0, 0, 1, 0);
```

パラメータ変更時は、このデフォルト値をベースに値の変更を行っています。

### カメラに関して試した処理： pan()・tilt（）
pan()・tilt（） は、公式リファレンス内に以下の説明ページがあります。

●reference | pan()
　https://p5js.org/reference/#/p5.Camera/pan

●reference | tilt()
　https://p5js.org/reference/#/p5.Camera/tilt

これらを使った処理は、キーが押された時に実行するようにしています。

左右の矢印キーが押された時は「pan(0.01)」・「pan(-0.01)」といった処理を行い、上下の矢印キーが押された時は「tilt(-0.01)」・「cam.tilt(0.01)」といった処理を行っています。

### カメラに関して試した処理： move()
move() は、公式リファレンス内に以下の説明ページがあります。

●reference | move()
　https://p5js.org/reference/#/p5.Camera/move

これを使った処理は `move(cameraX, 0, cameraZ)` という内容です。
cameraX と cameraZ は、何もキーが押されていなければゼロの値となり、特定のキーが押下された際に値が 10 や -10 になるようにしています。

## 今回使ったソースコード
以下に、今回の 3通りのお試しに用いたソースコードを載せておきます。

### camera() のパラメータ変更を行ったもの

```javascript
let cameraX = 0,
  cameraZ = 0;
const stepX = 10,
  stepZ = 10;

function setup() {
  const canvas = createCanvas(500, 400, WEBGL);
  canvas.position(0,65);
  createElement('h2', '「camera()」のパラメータを変更');

  ambientLight(255, 255, 255);
}

function draw() {
  background(180);

  if (keyIsDown(LEFT_ARROW)) {
    cameraX += stepX;
  } else if (keyIsDown(RIGHT_ARROW)) {
    cameraX -= stepX;
  }

  if (keyIsDown(UP_ARROW)) {
    cameraZ -= stepZ;
  } else if (keyIsDown(DOWN_ARROW)) {
    cameraZ += stepZ;
  }

  camera(cameraX + 0, 0, cameraZ + height / 2 / tan(PI / 6), 0, 0, 0, 0, 1, 0);

  drawObjects();
}

function drawObjects() {
  push();
  translate(-width / 4, -height / 4, 0);
  fill(0, 0, 255);
  box(80);
  pop();

  push();
  box(80); // 色指定なしだと「ambientLight」の色に
  pop();

  push();
  translate(width / 4, height / 4, -100);
  fill(200, 100, 150);
  box(80);
  pop();
}

function keyPressed() {
  if (key == " ") {
    cameraX = 0;
    cameraZ = 0;
  }
}
```

基本的には上で説明した処理を実装した形ですが、一番下の部分の `keyPressed()` で初期位置に戻す処理も加えています。

### pan()・tilt（） を使ったもの

```javascript
let cam;

function setup() {
  const canvas = createCanvas(500, 400, WEBGL);
  canvas.position(0,65);
  createElement('h2', '「pan()・tilt（）」を利用');
  
  cam = createCamera();
  ambientLight(255, 255, 255);
}

function draw() {
  background(205);

  if (keyIsDown(LEFT_ARROW)) {
    cam.pan(0.01);
  } else if (keyIsDown(RIGHT_ARROW)) {
    cam.pan(-0.01);
  }

    if (keyIsDown(UP_ARROW)) {
    cam.tilt(-0.01);
  } else if (keyIsDown(DOWN_ARROW)) {
    cam.tilt(0.01);
  }
  
  drawObjects();
}

function drawObjects() {
  push();
  translate(-width / 4, -height / 4, 0);
  fill(0, 0, 255);
  box(80);
  pop();

  push();
  box(80);
  pop();

  push();
  translate(width / 4, height / 4, -100);
  fill(200, 100, 150);
  box(80);
  pop();
}

function keyPressed() {
  if (key == " ") {
    camera(0, 0, height / 2 / tan(PI / 6), 0, 0, 0, 0, 1, 0);
  }
}
```

### move() を使ったもの

```javascript
let cam,
  cameraX = 0,
  cameraZ = 0,
  moveParamX = 10,
  moveParamZ = 10;

function setup() {
  const canvas = createCanvas(500, 400, WEBGL);
  canvas.position(0,65);
  createElement('h2', '「move()」を利用');
  
  cam = createCamera();
  ambientLight(255, 255, 255);
}

function draw() {
  background(220);

  if (keyIsDown(LEFT_ARROW)) {
    cameraX = -moveParamX;
  } else if (keyIsDown(RIGHT_ARROW)) {
    cameraX = moveParamX;
  } else {
    cameraX = 0;
  }
  if (keyIsDown(UP_ARROW)) {
    cameraZ = -moveParamZ;
  } else if (keyIsDown(DOWN_ARROW)) {
    cameraZ = moveParamZ;
  } else {
    cameraZ = 0;
  }

  cam.move(cameraX, 0, cameraZ);

  drawObjects();
}

function drawObjects() {
  push();
  translate(-width / 4, -height / 4, 0);
  fill(0, 0, 255);
  box(80);
  pop();

  push();
  box(80);
  pop();

  push();
  translate(width / 4, height / 4, -100);
  fill(200, 100, 150);
  box(80);
  pop();
}

function keyPressed() {
  if (key == " ") {
    camera(0, 0, height / 2 / tan(PI / 6), 0, 0, 0, 0, 1, 0);
  }
}

```

## 実行結果
冒頭で掲載したツイートにもありましたが、実行結果は以下のとおりです。

<iframe width="560" height="315" src="https://www.youtube.com/embed/S2TGkwNx2-c" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## おわりに
今回の記事の中で「3通りの処理結果の比較や、違いが生じる理由（各処理の仕様の違い）」も扱おうと思っていたのですが、ここまででけっこうな分量になった感じもあるので、続きは別の記事に分けて書こうと思います。