前書き
前回投稿した【数学】知ると出来る事が広がる ベクトル 10の知識
の続きの記事になります。
前回はベクトルの基本的な解説をしました。
今回は実際にJavaScriptを使ってベクトルをクラスとして作るという内容です。
ベクトルクラスはそこら中にありふれており
この記事の作り方が正解というものでもありませんが
1つの参考例として記事にしています。
最終的なコードはgithubにも上げてあります。
また動画も用意しているので動画派の方はこちらの動画をどうぞ
概要
- 2次元ベクトルなのでクラス名はVector2とします。
- ベクトルの足し算、引き算、実数倍、正規化、内積、外積など
前回の記事で解説した内容を定義していきます。
クラス定義
// x成分とy成分を持つ2次元ベクトル
class Vector2 {
}
このクラスの中に各種定義を追加していきます。
コンストラクタ
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
- xとyの2つのメンバ変数を持ちます。
xとyを設定するメソッド
// xとyをセットする
set(x, y) {
this.x = x;
this.y = y;
return this;
}
引数にもらったx
とy
を代入するだけですが
メソッドチェインできるようにreturn this
しときます。
ベクトルの複製
clone() {
return new Vector2(this.x, this.y);
}
自身と同じ内容のベクトルを生成して返すだけです。
後ほど使います。
ベクトルの加法(足し算)
add(v) {
this.x += v.x;
this.y += v.y;
return this;
}
正直オペレーターのオーバーロードを使いたいですが
JavaScriptにはオペレーターのオーバーロード機能はないので普通にメソッドで定義します。
引数でもらったベクトルのx
とy
を自身に加算するだけで完成です。
メソッドチェインのためにreturn this;
しておきます。
ベクトルの減法(引き算)
sub(v) {
this.x -= v.x;
this.y -= v.y;
return this;
}
引き算は逆ベクトルの足し算で表現できますが
逆ベクトル作ってから足すよりコストが安いので定義しておきます。
引数にもらったx
とy
を自身から引くだけで完成です。
メソッドチェインしたいので...以下略
ベクトルの実数倍
times(num) {
this.x *= num;
this.y *= num;
return this;
}
ベクトルを何倍にするかを引数(num
)にもらって、x
とy
をnum
倍すれば完成です。
逆ベクトル
get inverse() {
return this.clone().times(-1);
}
計算によって導出できる値であり、副作用がない場合はプロパティとして定義していきます。
処理は逆ベクトルはx
とy
の符号を反転させるだけです。
逆ベクトルを求めた際にx
とy
の内容は変えたくないので
clone()
で複製したベクトルを-1
倍します。
ベクトルの大きさ
get magnitude() {
const { x, y } = this;
return Math.sqrt(x**2 + y**2);
}
$magnitude = \sqrt{x^2+y^2}$
ベクトルの大きさはピタゴラスの定理にて導出します。
ベクトルの正規化
get normalized() {
const { x, y, magnitude } = this;
return new Vector2(x/magnitude, y/magnitude);
}
ベクトルの正規化はx
とy
をベクトルの大きさで割るだけなのでこれで完成です。
正規化前のベクトルを$\vec{v}$とすると
正規化した後のx成分 = $\frac{x}{|\vec{v}|}$
正規化した後のy成分 = $\frac{y}{|\vec{v}|}$
staticな足し算、引き算、実数倍
static add(v1, v2) {
return v1.clone().add(v2);
}
v1
を複製したものにv2
を足し、副作用のない足し算を定義しておきます。
// ①インスタンスメソッド:v1の値が変わってしまう
v1.add(v2);
// ②クラスメソッド:v1とv2の値は変わらない
const v3 = Vector2.add(v1, v2)
②が欲しいケースも多々あるため定義しています。
上記の理由からsub
とtimes
のstatic版も定義します。
static sub(v1, v2) {
return v1.clone().sub(v2);
}
static times(v1, num) {
return v1.clone().times(num);
}
ベクトルの内積
static dot(v1, v2) {
return (v1.x * v2.x + v1.y * v2.y);
}
内積はそれぞれのx成分とy成分をかけて足す事で求められます。
ベクトルの内積はよく以下の公式で表されますが
$|\vec{v1}||\vec{v2}|\cos(\theta)$
この公式では二つのベクトルのなす角θがわからないと内積を計算できません。
2つのベクトルからθを求めることもできますが、そんなことしなくても内積は求められるので
一般的にこちらの計算方法が使われると思います。
なぜこれで内積が求まるのか?は別のサイトにお任せします。
もしくは後日動画をあげるかもしれません。
ベクトルの外積
static cross(v1, v2) {
return (v1.x * v2.y - v1.y * v2.x);
}
外積は双方のベクトルのx,yをかけて引いたモノになります。
内積と同様、なぜこれで外積が求まるのかは他のサイトにおまかせしますが
外積はv1
を左に90度回転させたベクトルとの内積と考える事もできるので
なぜこれで計算できるの?という理屈は内積と同じように考える事ができます。
その他、便利メソッドの定義
ベクトルとしての基本的な振る舞いは以上として
その他、定義しておくと便利なメソッドをいくつか定義しておきます。
2点間の距離
static distance(v1, v2) {
return Vector2.sub(v1, v2).magnitude;
}
2つのベクトルの差を取り、その差分ベクトルの大きさ = 2点間の距離です。
よく使うのでstaticで定義しておいてもいいと思います。
2つのベクトルが平行か?
static isParallel(v1, v2) {
return (Vector2.cross(v1, v2) === 0);
}
外積の結果が0であれば2つのベクトルは平行と言えます。
ただプログラムには浮動少数点誤差があるので、判定は0と一致ではなく
Math.abs(外積) < 0.0001
など
状況に合わせて、緩めに判定するのが現実的です。
2つのベクトルが垂直か?
static isVertical(v1, v2) {
return (Vector2.dot(v1, v2) === 0);
}
内積の結果が0であれば2つのベクトルは垂直と言えます。
ただプログラムには浮動少数点誤差がうんぬん...略
よく使うベクトル
ゼロベクトルやxとyが1のベクトルなどよく使うベクトルもstaticで定義しておくと便利です。
static get zero() {
return new Vector2(0,0);
}
static get one() {
return new Vector2(1, 1);
}
static get right() {
return new Vector2(1, 0);
}
static get left() {
return new Vector2(-1, 0);
}
static get up() {
return new Vector2(0, 1);
}
static get down() {
return new Vector2(0, -1);
}
これでVector2.zero
とすればゼロベクトルが
Vector2.right
とすれば右向きの単位ベクトルが手に入ります。
基本となるベクトルはあらゆる計算で使う事が多いのでこのように定義しておきます。
終わり
以上でベクトルクラスの作成は完了です。
他にも定義しておくと便利なメソッドはありますが
今回はこのくらいにしておきます。
またVector2クラスは実質、xとyの2つの情報しか持っていません。
この2つからこれだけ多くの情報が得られるというのも面白いですね。