Subterranean Flower

JavaScriptの三角関数とcanvasで円運動アニメーションを作る

Author
古都こと
ふよんとえんよぷよぐやま!!!

アニメーションにおいて三角関数を使うと、面白い動きを作ることができます。JavaScriptを使って、三角関数とcanvasで円運動アニメーションを作ってみましょう。

三角比・三角関数のおさらい

アニメーションにおいて、三角関数を使うと、様々な複雑な動きを表現することができます。

こんな動きも。

しかし、数学的な物事を扱うには、ある程度の知識が必要です。まずは三角比・三角関数について、軽くおさらいしておきましょう。

直角三角形と比

三角比・三角関数について知るには、まず直角三角形について知る必要があります。直角三角形って覚えてますか?ひとつの角の角度が90度の、あれです。あいつです。

直角三角形には面白い性質があります。それは次のようなものです:

角度が決まると、辺の比も決まる。

これは本当でしょうか?

実際に確かめてみる

幾何学的に込み入った話にもできますが、今回はカジュアルに行きましょう。実際に測って確かめればいいのです。

図のような直角三角形ABCを考えます。今回は大きい方と小さい方のふたつ用意しました。角Aが45度、角Bが45度、角Cが90度の直角三角形です。

このとき例えばAB:BCという比を考えます。すると以下のようになります:

  • 左の小さい三角形 → AB:BC = √2:1
  • 右の大きい三角形 → AB:BC = 2√2:2 = √2:1

全く同じになりました!他の辺はどうでしょう?AB:ACはどちらの直角三角形も√2:1になりますし、AC:BCはどちらも1:1になります。大きさの違う三角形なのに、寸分の狂いもなく同じです!

他の角度でも試してみましょう。

今度は角Aが30度の場合です。AB:BCを見てみるとどちらの直角三角形も2:1ですし、AB:ACは2:√3、AC:BCは√3:1となっています。45度のときと同じく、ふたつの直角三角形の辺の比は同じになっています。

どうやら先ほどの「角度が決まれば辺の比も決まる」というのは正しそうです。そしてこれは45度や30度のときだけではありません。20度でも、60度でも、あらゆる角度について、それぞれの角度に対応した辺の比が存在します。

これを「三角比」と呼ぼう。比にも名前をつけよう

直角三角形の辺の比なので、これらのことを「三角比」と呼びます。また、AB:BCなどのそれぞれの比についても名前をつけましょう。

AB:BCのことをsin(サイン)、AB:ACのことをcos(コサイン)、AC:BCのことをtan(タンジェント)と呼ぶことにします。

先ほどの45度の例なら、sinは√2:1、cosは√2:1、tanは1:1になります。

三角比と三角関数

直角三角形の辺の比(三角比)は角度によって決まる」ことがわかりました。ここでちょっと言い回しを変えてみましょう。次のように言い換えられるはずです:

三角比は角度”だけ”で決まる。

先ほどの例を思い出してください。角Aの角度が45度のとき、三角形の大きさに関わらずsinは√2:1となりました。cosやtanについても同様です。つまり「それはどのような大きさの三角形か?」という情報は必要なく、角度だけわかれば、三角比を導き出すことができるのです。

「角度」がわかれば「三角比」がわかる、ということは、これはもはや関数と言っても差し支えないはずです。よって関数っぽく書いてみましょう。

  • sin(45°) = √2:1
  • cos(45°) = √2:1
  • tan(45°) = 1:1

JavaScript風に書くとfunction sin(degree)で、この関数は角度を入力として三角比を出力します。このようにして、「角度」と「三角比」の関係を関数として表したものを、「三角関数」と言います。

ここまでのまとめ

ここまでで、以下のことがわかりました:

  • 直角三角形の辺の比は、角度だけで決まる
  • この辺の比のことを三角比という
  • 角度だけわかれば、三角比が求められる
  • 角度と三角比の関係を表した関数を、三角関数という

んで何に使うの?

ここまでひたすら直角三角形の話ばかりしてきました。しかし現実には直角三角形などほとんど見当たらず、応用範囲は狭そうに思えます。

でも、ちょっと待ってください。直角三角形が見当たらないのなら、勝手に直角三角形を作って応用してしまえばいいのです。

例題:円周上の座標

問題:図のような半径5の円がある。このとき円周上の点Pの(x, y)座標を求めよ。

直角三角形を使って例題を解く

例題は円周上の点Pのxy座標を求めるというものです。普通に考えるとなかなか難しい問題ですが、直角三角形を使うと簡単に解くことができます。

まず点Pからx軸に垂線をおろします。このとき垂線とx軸の交点をQとします。

すると直角三角形OPQができました!直角三角形OPQは、OPの長さが5であることがわかっています。なおかつ角Oが45度であることもわかっているので、三角比から、辺の比がわかります。よって直角三角形のすべての辺の長さがわかります。

さらに、図を見てみると、この直角三角形のOQの長さはそのままPのx座標となり、PQの長さがそのままPのy座標となっています。つまり直角三角形の辺の長さを求めることでPの座標がわかります。

実際に計算してみましょう。まずはPのy座標を求めてみます。

OP:PQ = sin(45°) = √2:1 = 5:y

まずは上のような式が成り立ちます。OP:PQはsinと呼ぶことにしていました。ここで三角比は角度だけで決まるので、sin(45°)は√2:1に決まります。

そしてOPの長さは5、PQの長さはy(まだわかっていない)なので、OP:PQ = 5:yと言えます。これをyについて解いて行きましょう。

√2:1 = 5:y

となり、

√2y = 5

よって

y = 5/√2

これでy座標が求まりました!x座標についても同様に計算してみましょう。

OP:OQ = cos(45°) = √2:1 = 5:x

よって

x = 5/√2

したがって求める座標は(x, y) = (5/√2, 5/√2)となります。

このようにして、何もないところに無理やり直角三角形を作ってやることで、様々な問題を簡単に解くことができます。

角度の表し方:ラジアン

今までは度数法を用いて角度を現してきました。しかし解析学やプログラミングにおいては度数法よりラジアンという表現方法が好まれます。

ラジアンは極めてシンプルで、180度をπ360度を2πとする角度の表し方です。つまり、度数法での角度を180で割って、πをかければラジアンになります。

  • 180° = 180 / 180 * πラジアン = πラジアン
  • 360° = 360 / 180 * πラジアン = 2πラジアン

変換は簡単にできるのでぜひ覚えておきましょう。

三角関数を使った円運動アニメーション

さて、ようやくJavaScriptの話題に入ることができます。三角関数を利用した円運動アニメーションを作ってみましょう。

これ

骨組みを作る

最初に骨組みを作りましょう。だいたい以下のような感じになると思います。JavaScriptにおいて角度はラジアンで表現されることに注意してください。

// canvasを作ってHTMLに突っ込む。
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = 500;
canvas.height = 500;
document.body.appendChild(canvas);

// 円運動の中心座標。
// 今回はcanvasの真ん中を中心に移動する。
const centerX = 250;
const centerY = 250;

// 円を描画する中心からの距離。
const distanceFromCenter = 150;

// 表示する円のサイズ。
const circleSize = 30;

// 変化させていくパラメータ。
// angleRadを増加させていき、
// それに伴いxとyが変化していくようにする。
let x = centerX;
let y = centerY;
let angleRad = 0;

// メインループ
function loop(timestamp) {
  // 描画内容を消去する。
  context.clearRect(0, 0, canvas.width, canvas.height);
  
  // angleRadを1度ずつ変化させていく。
  // 1度はMath.PI/180ラジアン。
  angleRad += 1 * Math.PI / 180;

  // ここで座標を変化させていく。
  // x = ???
  // y = ???

  // 求めた座標に円を描画する。
  context.beginPath();
  context.arc(x, y, circleSize, 0, Math.PI * 2);
  context.fill();

  window.requestAnimationFrame(loop);
}

window.requestAnimationFrame(loop);

x座標とy座標を求める

これでangleRadが1度ずつ変化していくプログラムを組むことができました。しかしangleRadは描画に反映されていません。angleRadの値に応じてx座標y座標が変化していくように変更してみましょう。

どうやって座標を求めればいいのでしょうか?先ほどの円周上の点の問題を思い出してください。今回もあれと同じです。円の半径がdistanceFromCenterに、角度がangleRadに変わっただけです。

そして角度に応じたsinやcosの値はJavaScriptが勝手に求めてくれます(やったー!)。よって円周上の点の例と同じようにしてx座標とy座標を求めることができます。

OP:OQ = cos(angleRad) = distanceFromCenter:x

次に比の表現を計算のため分数に直します。よって

cos(angleRad) = x / distanceFromCenter

これをxについて解くと、

x = distanceFromCenter * cos(angleRad)

となります。

同様にyについても、

OP:PQ = sin(angleRad) = y / distanceFromCenter

となり、

y = distanceFromCenter * sin(angleRad)

としてyが求まります。

しかし求めた座標は原点(0, 0)を中心とした座標です。今回は(centerX, centerY)が中心なので、その分だけ並行移動しておきましょう。

つまり求める座標は、

  • x = distanceFromCenter * cos(angleRad) + centerX
  • y = distanceFromCenter * sin(angleRad) + centerY

となります。これを先ほどのコードに挿入してみましょう。

コードを完成させる

求めた座標をコードに反映してみましょう。JavaScriptにおいてsinはMath.sin関数を、cosはMath.cos関数を使うことで求めることができます。

// canvasを作ってHTMLに突っ込む。
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = 500;
canvas.height = 500;
document.body.appendChild(canvas);

// 円運動の中心座標。
// 今回はcanvasの真ん中を中心に移動する。
const centerX = 250;
const centerY = 250;

// 円を描画する中心からの距離。
const distanceFromCenter = 150;

// 表示する円のサイズ。
const circleSize = 30;

// 変化させていくパラメータ。
// angleRadを増加させていき、
// それに伴いxとyが変化していくようにする。
let x = centerX;
let y = centerY;
let angleRad = 0;

// メインループ
function loop(timestamp) {
  // 描画内容を消去する。
  context.clearRect(0, 0, canvas.width, canvas.height);
  
  // angleRadを1度ずつ変化させていく。
  // 1度はMath.PI/180ラジアン。
  angleRad += 1 * Math.PI / 180;

  // ここで座標を変化させていく。
  x = distanceFromCenter * Math.cos(angleRad) + centerX;
  y = distanceFromCenter * Math.sin(angleRad) + centerY;

  // 求めた座標に円を描画する。
  context.beginPath();
  context.arc(x, y, circleSize, 0, Math.PI * 2);
  context.fill();

  window.requestAnimationFrame(loop);
}

window.requestAnimationFrame(loop);

これで完成です!