読者です 読者をやめる 読者になる 読者になる

nabeliwo note

5万人に1人になる。

【three.jsのお勉強 #2】一旦2次元に立ち返る

three.jsのお勉強シリーズ


  1. 入門してみる
  2. 一旦2次元に立ち返る ← 今日はこれ
  3. WebGL入門者の会に行ってきた

やったこと


WebGLの勉強ということで3次元の考え方だったり計算だったりを学んでいるのですが、そういえば前に2次元のベクトルとかそこらへんの話を勉強して作ってみたりしたけれど全然覚えてねえやってことに気付いてしまったのでちょっとおさらいしてみました。

まあ3次元の計算をするために2次元の知識が必要かと言われるといらない気もするので決して道をまっすぐ進む中での項目というわけではないので、ただただ振り返りたいなっていう気持ちから回り道しております。

使った教材はこれ。

HTML5 Canvas

HTML5 Canvas

canvasについての本だしそして若干古いのですが、これの5章の「数学と物理学とアニメーション」ってとこがとても勉強になります。
なので今回はそこで取り扱われているサンプルを写経しつつ覚えておきたいとこをメモとして残していきたいと思います。

※今回書くことは全てx軸y軸のみの2次元上での計算の話です。WebGL勉強シリーズの一つなのでWebGLカテゴリーにしちゃったけれど3次元全然関係ないです。そしてちなみに僕は学生から卒業した瞬間勉強してきた数学の知識が全て抜け落ちたためここに書くことはとても初歩的な話になります。

覚えておきたいとこ


2地点間の直線距離

ピタゴラスの定理 を使うことで2地点間の直線距離を測ることができます。
公式は A² + B² = C² です。

x軸y軸の座標に置き換えると、

2地点間の距離は、(第2の地点のx値から第1の地点のx値を引いた差の2乗)と(第2の地点のy値から第1の地点のy値を引いた差の2乗)を合算した値の平方根に等しい。

ってことになります。
これはさすがに僕も覚えていた。けれどよく使いそうでぱっと出てこなそうなのでしっかり記憶しておきたい。

JavaScriptだと2乗は x ** 2 で求められるし、平方根Math.sqrt(x) で求められる。

角度の単位

角度の単位は「度」ではなく ラジアン を使います。
ただ、数値自体は「度」を使った方がわかりやすいため、角度を扱う場合は毎回「度」からラジアンに変換する必要があります。

公式は ラジアン = 度 * Math.PI / 180 です。

三角法

ベクトル移動の処理を書く場合に三角法が必要になります。
その前にまず、ベクトルの定義は以下のようになります。

大きさと向きを有する量

2地点間の距離でのオブジェクトの移動は難しい考え方をすることなく書くことができるのですが、開始地点だけがあって終了地点がない場合のオブジェクトの移動はちょっとだけ難しくなります。その場合は、ベクトルを使って処理します。
そしてベクトルに沿った動きを計算する際には、コサインとサインを使うことができます。
簡単に言うと コサインはxに関連 し、 サインはyに関連 するって考えれば良いようです。

つまり、1フレームあたりに移動する距離(x軸)は以下のようにわりだすことができます。

Math.cos(radians) * speed

radiansはベクトルの向きで、speedはベクトルの大きさになります。
y軸の移動距離は、Math.cos()をMath.sin()に変更することでわりだせます。

壁との衝突

壁(平面)にぶつかったときは、 入射角と反射角は等しい という反射角の法則を使って、ベクトルの向き(角度)を変更してやる必要があります。
x軸の壁にぶつかった場合は、現在のベクトルの角度を180から引き 、y軸の壁にぶつかった場合は 現在のベクトルの角度を360から引く ことで新たなベクトルの角度をわりだすことができます。

壁との衝突判定自体は簡単なので省きます。

オブジェクト同士の衝突

弾性衝突 、つまり運動量保存の法則を実現するようにオブジェクト同士の衝突後のベクトルの変化の処理の書き方。

衝突判定

まずはそもそもオブジェクト同士が衝突するかどうかを求めないといけないです。
2つのオブジェクトの次のフレームの地点同士を結ぶ直線距離を ピタゴラスの定理 を使って求めます。2つのオブジェクトではなく、2つのオブジェクトの次のフレームの地点ってのが大事です。ぶつかっているかではなく次のフレームでぶつかるかどうかを判定しないとくっついた状態もしくは食い込んだ状態になってしまうからです。

直線距離を求めることができたら、その距離と2つのオブジェクトの半径の和を比較して、直線距離の方が短いもしくは等しい場合は衝突することがわかるので、2つのオブジェクトのベクトルを変更してあげる必要があります。

ベクトルの変更

ここで出てきた公式が一応「公式だからこうなる」ってことでわかったけれど実際の流れが見えていないです…。

ベクトルの変更に必要な値は、2つのオブジェクトの質量と衝突時の速度と角度です。これらの値を使って衝突の角度を求めて2つのオブジェクトのベクトルを回転させて速度を更新します。

衝突の角度を求めるには Math.atan2() を使います。中身がいまいちわかってないですが、2地点の座標を引数として渡すことで衝突の角度を出せます。
とりあえずタンジェント逆関数であるということはわかった。

オブジェクトの現在の角度から衝突の角度を引いて、それのコサイン(x軸の場合)かサイン(y軸の場合)に速度をかけたものが新たな角度になります。
そして、運動量保存の法則に基づいて新たな速度をわりだすのですが、ここの公式がちょっと難しいです。

velocity1 = ((mass1 - mass2) * velocity1 + 2*mass2 * velocity2) / (mass1 + mass2)
velocity2 = ((mass2 - mass1) * velocity2 + 2*mass1 * velocity1) / (mass1 + mass2)

1と2の数字はそれぞれのオブジェクトを示しています。massは質量、velocityはベクトルです。

そして最終的な速度が求まったところで衝突の角度が維持されるように回転し直すっていうところでまた計算が出てきたのですがここらへんちんぷんかんぷんになりました。
一応書き方としては知ったので、こうすれば衝突後の値の変更はできるってのはわかったのですが理解からは程遠い…。数学をしっかり勉強しなきゃいけないっぽい…。誰か助けて…。

円運動

一定の円に沿って均一的に移動するオブジェクトの位置は以下の方程式を使って求めることができます。

x = 円の半径 * コサイン(角度)
y = 円の半径 * サイン(角度)

これを使いつつ、フレームごとに角度を微妙に変えていけば同じところを回転し続けるオブジェクトが作れる。

サンプルつくってみました


最後に、勉強したことを応用して簡単なサンプルを書いてみました。
書いてみたと言っても、1年半くらい前に作ったものをソースコード見直してもう一度書いてみただけのですが…。

2Dでの数学と物理学とアニメーション

canvas領域をクリックするとランダムなサイズと色と角度をもったボールが生成されます。
下のボタンを押すとボールに重力と摩擦と弾性が生じます。

まあそれだけの簡単なサンプルなのですが、ここまでやったら大体の2Dの基礎はおっけーな気がしました。

おわりに


今回は2D上でオブジェクトを動かしました。
オライリーのサンプルはなかなか出来がよくって順番通りこなしていけば基礎が身につくのでおすすめです。
2Dで学んだことは3Dでも活かせそうな気配がびんびんだったのでここで身につけた基礎をひっさげてこれ以降はまたthree.jsの勉強に戻っていきたいと思います。

今回サンプルをやってみてのソースコードGitHubにあがっております。

threejs-suburi/2d_canvas at master · nabeliwo-suburi/threejs-suburi