p5.jsでEasyCamのようなカメラのリセットを実装する

# はじめに
　[p5.EasyCam](https://github.com/freshfork/p5.EasyCam)
を使うとp5.jsのwebGLでカメラを扱うことができます。挙動についてざっくり説明すると、現在のp5.jsのorbitControl()のfreeRotationの挙動と全く同じ...ではないですが、大体そんな感じです。若干挙動の不具合があることを除けば便利です（次のデモを見ればわかりますがマウスダウンのあと外にドラッグしてマウスアップするとカメラが勝手に動いてしまう不具合が確認されています、これは現時点でも直されていません。：
　[p5.EasyCam EXAMPLE (p5.Editor)](https://editor.p5js.org/dark_fox/sketches/K4TrWmy99)
また通常の水平・垂直回転ができないのもp5.EasyCamの不便な点です。スケッチによってはそっちの方が便利です。
　それはさておき、ダブルクリックでリセットされるのは便利かもしれません。p5.jsでも実装次第で可能なので、それを紹介します。
　もともとp5.Cameraのslerpにそのデモが置いてあったのですが、何者かによって削除されてしまったので、ここで再度紹介せざるを得なくなってしまったという経緯があります。
　[p5.Camera.slerp](https://p5js.org/reference/p5.Camera/slerp/)

## コード全文
　[カメラリセット](https://editor.p5js.org/dark_fox/sketches/FtjF6IvVE)
```js
let cam, slerpCam, defaultCam;
let resetCount = 30;

function setup(){
  createCanvas(400, 400, WEBGL);
  defaultCam = createCamera();
  slerpCam = createCamera();
  cam = createCamera();
  cam.camera(0, 0, 200*sqrt(3), 0, 0, 0, 0, 1, 0);
  cam.perspective(PI/3, width/height, 20*sqrt(3), 2000*sqrt(3));
  defaultCam.set(cam);
}

function draw(){
  if(resetCount < 30){
    slerpCam.slerp(cam, defaultCam, resetCount/30);
    setCamera(slerpCam);
    resetCount++;
    if(resetCount===30){
      cam.set(defaultCam);
    }
  } else {
    setCamera(cam);
    orbitControl(1,1,1,{freeRotation:true});
  }
  background(0);
  lights();
  fill(255);
  box(100);
}

function doubleClicked(){
  if(resetCount === 30){ resetCount = 0; }
}
```

分かりにくいですが、リセットはダブルクリックにより実行されています。
なお当然ですが、ダブルクリックではなくたとえばキー操作とか、他のトリガーを使うことももちろん可能です。実装次第でいくらでも工夫できます。

<iframe width="560" height="315" src="https://www.youtube.com/embed/rZljF_M8A28?si=JlyH0SWQQhf-LqFz" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

## カメラの用意
　cam,defaultCam,slerpCamという3つのカメラを用意します。camは描画に使うカメラで、orbitControl()でいじるのは、これです。defaultCamは初期設定を保存するためのカメラ、slerpCamはリセット中に使用するカメラです。このカメラにorbitControl()は適用されません。camを最後に作っているのは、仕様により最後にcreateしたカメラが描画に使われるからです。
　カメラの設定の仕方はややこしいでしょうか？デフォルトでは高さ800のところにありやや不自然なため、独自に設定させてもらいました。ごめんなさい。

## リセットの仕組み
　ダブルクリックするとカウントが0になります。カウントが30より小さいうちは、その値の30までの割合で、ダブルクリックした時のカメラとデフォルトのカメラの間で補間が為されます。slerpによって、です。そしてその間はその補間結果としてのカメラが使われます。すべてperspectiveなので補間に関しては問題ないです（デフォルトではperspective）。カウントが30になったらデフォルトカメラがセットされて元に戻ります。カウントが30の間は、通常のカメラをorbitControl()でいじるだけのコードになります。
　30はリセットに要するカウントです。20でも、60でも、10でも、自由に決めることができます。また、このコードではresetCount/30という形で線形補間していますが、この値を加工することで自由にイージングを掛けることが可能です。説明は以上です。
　...
　なぜダブルクリックで単純にリセットされる仕様にしなかったのか？
　いくつか理由があります。まず単純に時間が足りなかったのとそれを実装するための仕様変更が煩雑すぎて手に負えなかったことなどです。またこれを見ればわかりますがリセットの正解が分かりません。自由度を持たせたいと思いました。あとはそうですね、カメラのslerpはカメラのリセットの他にも活用できる可能性があるため、実装において最も困難なところだけを解決する形がいいのではないかと思いました。

# おわりに
　ここまでお読みいただいてありがとうございました。
　...
　カメラのリセットは何のために実装するのか？何が面白いのか？
　同じ質問をEasyCamの作者の人にしてみれば何かわかるかもしれません。ちなみに自分にはわかりませんでした。
　Threeではメソッドを使うことでリセットを実行できるようですが、詳しくないので分かんないです。

## 追記：vRoidHubのカメラリセットの仕様
　最後に、自分がorbitControlの実装の際に参考としたvRoidHubのリンクを張っておきます。しらたまくんかわいい...
　[シラたま](https://hub.vroid.com/characters/6816271906710342450/models/2484253826834032825?utm_source=pixiv&utm_medium=profile&utm_campaign=pixiv-profile)
　調べると一応Threeのようですが、リセットが独特の仕様なんですよね。回した分だけ戻るので、おそらく単純なクォータニオンの補間ではないですね。すごく気になります...
　まず横回転ですが、これは縦回転の角度を制御するために軸となるベクトルを用意しておいてその周りに回転するんですよね。で、このベクトルはどんなに回しても動きません。なのでグローバルですね。一方縦の回転は純粋にsideのまわりなのでローカルですね。ローカルとグローバルなので乗算の方向が違う、ゆえに可換。そんなところかと。あとは角度の累積をメモしてそれを元に回しているんだと思います。最短経路を取らないので、くるくるするわけですね。数式で書くと、まず最初の座標系をあらわすクォータニオンを$q$として、上を表すベクトルを$e$とし、その周りに$\phi$回転したとし、縦回転の累積を$\theta$として
```math
(\cos\frac{t\phi}{2}+e\sin\frac{t\phi}{2})q(\cos\frac{t\theta}{2}+i\sin\frac{t\theta}{2})
```
において$t$を$1$から$0$まで動かせばこのような補間になるんだと思います。もちろん
```math
e=e_x i+e_y j + e_z k
```
といういつもの同一視です（四元数体への三次元空間の埋め込み）。中心の位置については線形補間だと思います。