様々な大きさの円を重ならないように描く

あるテレビ撮影現場で解像度1920×1080のスクリーンに700個程の円を敷き詰めて描き動かすという要件があり JavaScript の Canvas を使ったのですが、円の座標を取得する際にブラウザの応答がなくなってしまう問題を避けるために注意した点を二つ書き留めておきます。

この画面を避ける

一つ目は重なり判定に Math.sqrtMath.pow を使わないことです。二つの円が重なる条件を言語化すると「中心点からの距離が、半径の合計よりも小さい」となって、直訳すると Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)) < r2 + r1 と表すことができるのですが、三平方の定理を平素に解釈し関数を使わず (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) < (r2 + r1) * (r2 + r1) とした方が処理が速くなります。

参考

二つ目は Web Workers の利用です。表示する円座標を格納しておく配列の数が上限数に到達するまで繰り返す部分 while(circles.length < circlesMax){...} を丸ごと worker.jsself.addEventListener('message', (e) => {...}); に記述しました。座標計算が終わるまで「読み込み中…」を表示をするのは UI としても良いし、処理時間の計測をしておき長い場合に中断しやり直すということも可能になり便利なこと極まりありません。

const worker = new Worker('./worker.js');
 
worker.addEventListener('message', (e) => {
  circles = e.data;
  circles.forEach(function(circle){
    drawCircle.call(this, circle.x, circle.y, circle.r);
  });
});
 
worker.postMessage({
  radiusMax: /*最大半径*/,
  radiusMin: /*最小半径*/,
  circlesMax: /*最大表示円数*/
});

これに関連して注意したのは、動作実機はウェブサーバがなくデスクトップ等に置いたファイルを実行する状況から同一生成元ポリシーによるエラーになってしまうので、Chrome にオプションを付けて起動することを予め伝えておく必要があります。

参考

実際には円同士を線で結んだり円や線の増減アニメーションがあったりで、今回初めて書いた Web Workers は参考したコードにもある Promise との併用がとても効果的になる場面が増えてくるのではないかと思いました。

投稿者: hkitago

個人事業主でウェブと iOS, Android アプリの開発者で一児の父親。JavaScript, ActionScript, AppleScript, PHP, SQL, ObjC, Swift, Java の読書実行試験運用管理を生業とし、Bind, Postfix, Apache を MacOS で使い、エディタは Vi, mi, Kod, Smultron, TextWrangler を経て Coda, Xcode, Android Studio といった IDE と CotEditor を重用しています。