感染シミュレーションの2

まあいつものことだけど、何か作ろうと思いたつと、頭の中はもうそのことばかり。 だから仕事をしないってことはないんだけど、仕事の最中でも、ちょっとした休憩で頭に浮かぶのはこっちだったりする。

とはいえ。

一定の領域の中で動かして、当たり判定をして、判定結果に応じて表示を変える。

つまるところこれだけなので、仕様面であまり考えることはない。

一応シミュレーションなので、動かした後は眺めるだけ。 なので、初期値として設定するパラメーターぐらいは変えられるようにしよう。 具体的には以下の項目。

こんな感じのコントローラーがあればいいだろう。

あとは接触の定義。 接触した時に感染するのだが、この接触をどう扱うか。 もう少し具体的に言うと、丸を人と見立てるわけだが、この丸と人との物理的な関係性をどう定義するか。

すぐに思いつくのは、大雑把に以下の2つ。

まあどっちにしても極端なモデル化ではあるが、現実に近いのは後者な気がするので、ここでは後者を採用する。

そして実装。

これまたいつものことだが、JavaScriptで作る。

丸を描くのも動かすのもcanvasでやるとして、動かすには座標の管理が、当たり判定やはみ出し判定には領域の管理が必要になる。 こうした基礎的なものから作っていく。

まずは座標、いや、ベクトルか。

位置を ( x, y ) として表現するのはもちろんだが、矩形領域の幅と高さも、速度も、同じ形式で表現できる。 この形式で表現できる何かのためのデータ構造なら、名前も座標よりはベクトルの方がしっくりくるだろう。

class Vct { constructor( x, y ) { this.x = x; this.y = y; } add( vct ) { this.x += vct.x; this.y += vct.y; } }

位置も速度も同じくベクトルで表現して、移動は、現在の位置に速度を足して次の位置とする。 この足し算に使う想定で add メソッドを用意している。

これが感染シミュレーションじゃなかったら、不変オブジェクトを意識して、こんな実装にしたかもしれない。

class Vct { #x; #y; constructor( x, y ) { this.#x = x; this.#y = y; } get x() { return this.#x } get y() { return this.#y } add( vct ) { return new Vct( this.x + vct.x, this.y + vct.y, ) } }

でも今回のような、つまり短い周期で連続して動かすような場合、移動の度に新しいインスタンスを生成するのは、流石にちょっと躊躇ってしまう。 今作ったインスタンスは、一瞬(60fpsのディスプレイなら0.017秒後)でゴミになるからね。 マシンスペック的には問題無いだろうけど、なんか勿体なくて。

ということで、メモリ効率を優先して、クラスのメンバーは全て public で通す。

次に矩形領域。

class Rct { constructor( pt, sz ) { this.pt = pt; // ::Vct this.sz = sz; // ::Vct } get xl() { return this.pt.x } // X Left get yt() { return this.pt.y } // Y Top get xr() { return this.pt.x + this.sz.x } // X Right get yb() { return this.pt.y + this.sz.y } // Y Bottom isXOverflow( rct ) { // rctが左右方向で自分からはみ出しているか return ( rct.xl < this.xl || this.xr < rct.xr ); } isYOverflow( rct ) { // rctが上下方向で自分からはみ出しているか return ( rct.yt < this.yt || this.yb < rct.yb ); } isOverlap( rct ) { // rctが自分と重なっているか return !( this.xr < rct.xl || rct.xr < this.xl ) && !( this.yb < rct.yt || rct.yb < this.yt ) } }

動き回る丸も、その丸が動き回れる領域も、この矩形領域クラスを継承して作る想定。

コンストラクタに渡しているのは、左上の座標となるベクトルと、幅と高さを成分とするベクトル。 これらを元に、矩形領域の左右端のX座標と上下端のY座標を返すメソッドも作っておいた。

シミュレーションで直接使う想定のメソッドは、はみ出し判定と当たり判定。

はみ出し判定は、丸が領域の端に来た時に跳ね返すため。 跳ね返りは、左右の壁からはみ出したら速度のX成分を、上下の壁からはみ出したら速度のY成分を、反転させることで実現する。 なのではみ出し判定も、左右方向と上下方向のそれぞれで用意している。

当たり判定は、丸同士の重なり合いの判定のため。 丸同士の重なり合いなので、ちゃんとやるなら中心間の距離と半径の和との比較だが、今回は丸同士が衝突しても跳ね返らないからね。 丸が表すのも周囲の空気というふわっとしたものだし、これでいいのだ。

基底とするクラスができたので、今日はここまで。