20: Vector3構造体の拡張

概要

単にxyzのfloatを含むVector3構造体を定義した場合、以下のような演算ができない。
Vector3 v = Vector3(1,2,3);  // (x,y,z) = (1,2,3) のベクトルを定義したい
Vector3 v2 = -v;             // (x,y,z) = (-x,-y,-z) したい
上記を行いたい場合、現状はこのような冗長な事をするしかない。
Vector3 = v;
v.x = 1;
v.y = 2;
v.z = 3;
Vector3 v2;
v2.x = -v.x;
v2.y = -v.y;
v2.z = -v.z;
今回は、コンストラクタやC++の演算子のオーバーロードを使用することにより、Vector3構造体を拡張する事を目標にやっていく。
一応18回の記事で作成したgeneral-movement.hを書き換えていく形でやっていくが、 18回の記事を見なくても理解できるようにはなっている。

コンストラクタで Vector3(1,2,3) のように定義できるようにする

現状Vector3は構造体として定義していて、クラスでは無いのでコンストラクタという用語が出てきた事に疑問を持つ方もいると思うが、 実質バイナリレベルで見ればクラスと構造体は同じで、クラスで出来る事は構造体でできると思ってよい (出来なかったら言語の仕様上実装されていないというだけ)。

なので、例えば以下のようにコンストラクタをVector3構造体に定義すれば、以下のようにして使用することができる。
typedef struct Vector3 {
	float x;
	float y;
	float z;
				
	Vector3(float _x, float _y, float _z) {
		x = _x; y = _y; z = _z;
	}
} Vector3;

// 使用例
Vector3 v(1,2,3);
Vector3 v = Vector3(1,2,3);

また、C++ではもっと便利な書き方があり、コンストラクタの引数の初期化は以下のように書ける。
typedef struct Vector3 {
	float x;
	float y;
	float z;
				
	Vector3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}
} Vector3;

// 使用例
Vector3 v(1,2,3);
Vector3 v = Vector3(1,2,3);
上記二つは同じ意味なので、好きな方を使えばよい。
最後に、コンストラクタを上記のようにだけ定義してしまうと、いつも通りに Vector3 v のように宣言できなくなってしまうので、 Vector3 v とした場合は(0,0,0)で初期化されるようにしておく。
typedef struct Vector3 {
	float x;
	float y;
	float z;

	Vector3() : x(0), y(0), z(0) {}
	Vector3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}

	Vector3 operator - () {
		x = -x;
		y = -y;
		z = -z;
		return *this;
	}
} Vector3;
変更後のgeneral-movement.hは、次の演算子のオーバーロードの変更も加えた上で、このページの末尾に置いてある。

演算子のオーバーロードで -v を可能にする

※ 下記内容はこの記事を参考にしているので、 さらに詳しく知りたい方はこの記事を参考にすると良いかと。
関数のオーバーロードは、別の引数で同じ名前の関数を定義する事で、つまりはある関数に別の機能を持たせるみたいなことができる。
C++では、例えば<<が入力ストリームと左シフト演算のどちらでも使えるように、演算子もオーバーロードできる。

演算子と一概に言っても、演算子には種類がある。
普通の演算子: 〇+〇 みたいに両端に数字/変数があるやつ
単項演算子:  〇++ や -〇 みたいに一つの数字/変数に対して適用するやつ
添え字演算子: 〇[1] みたいに配列とかで使う[]のこと
この3つの内どれかにより、演算子のオーバーロードの方法が若干異なるので、まずはどの種類なのかを区別する必要がある。 今回は、-vがやりたいので、単項演算子である-をオーバーロードする。

単項演算子のオーバーロード

単項演算子のオーバーロードは、以下のようなフォーマットで行う。
<型> operator <単項演算子> () {
	// 処理内容;
}
今回の例の場合は、以下のように定義すればよい。
typedef struct Vector3 {
	float x;
	float y;
	float z;

	Vector3() : x(0), y(0), z(0) {}
	Vector3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}

	Vector3 operator - () {
		x = -x;
		y = -y;
		z = -z;
		return *this;
	}
} Vector3;
ここで注意すべき点は、演算子のオーバーライドの関数の中では、演算対象はthisとして表現されるという点である。 つまり、上記のコードでは、this-vと書いたときのvの事である。 もっといえば、x = -xthis->x = -this->xを省略した物である。

ここまでの変更を加えたgeneral-movement.hをページの末尾に置いている。
また、前にInverse関数を書くためにgeneral-movement.cppを作成したが、 この演算子のオーバーロードをした以上必要無いので、今後はファイルごと消しちゃって構わない。

普通の演算子のオーバーロード

こっちは今の所特に必要ないが、普通の演算子のオーバーロードの方も書いておく。
ここでは、Vector3 v(1,2,3); Vector3 w(2,3,4); があった場合に、v+w(3,5,7)ができるようにする。

普通の演算子のオーバーロードは、以下のようなフォーマットで行う。
<型> operator <普通の演算子> (<二項目のオペランド>) {
	// 処理内容;
}
今回の例の場合は、以下のように定義すればよい。
typedef struct Vector3 {
	float x;
	float y;
	float z;
	
	Vector3() : x(0), y(0), z(0) {}
	Vector3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}

	Vector3 operator - () {
		x = -x;
		y = -y;
		z = -z;
		return *this;
	}

	Vector3 operator + (Vector3 v2) {
		x += v2.x;
		y += v2.y;
		z += v2.z;
		return *this;
	}
} Vector3;

// 使用例
Vector3 v(1, 2, 3);
Vector3 w(2, 3, 4);
Vector3 sumvw = v + w;  // (3, 5, 7)
ポイントは、a+bathisで表現されており、 bは引数として渡される方であると言う点である。

ソースコード

general-movement.h

typedef struct Vector3 {
	float x;
	float y;
	float z;

	Vector3() : x(0), y(0), z(0) {}
	Vector3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}

	Vector3 operator - () {
		x = -x;
		y = -y;
		z = -z;
		return *this;
	}
} Vector3;

typedef struct {
	float x;
	float y;
	float z;
	float w;
} Quaternion;
※ general-movement.cppはもう消しちゃってOK