No.003 ベクトル型について






この回ではベクトル型について説明します。 ...と言っても何ら特別な事はありません。 淡々と 3 次元ベクトル型のクラスを定義していきます。


メンバ変数
コンストラクタ
初期化用メソッド
プロパティ
各種演算
(ユーティリティとしての)クラス変数
Vector3 クラス
サンプルプログラム

3 次元のベクトル型なのでメンバ変数もシンプルに X,Y,Z だけです。 カプセル化の観点からすると、このようなむき出しのメンバ変数には 問題があるかとは思います(この実装の方が若干速いので)。 気になる方は 1 行目の #define PROPERTY_XYZ を有効化して使って下さい。
// #define PROPERTY_XYZ

using System;

namespace ToyBox {
	public class Vector3 {
			:
#if !(PROPERTY_XYZ)
		public float X,Y,Z;
#else
		float mX,mY,mZ;
		public float X {
			set { mX=value; }
			get { return mX; }
		}
		public float Y {
			set { mY=value; }
			get { return mY; }
		}
		public float Z {
			set { mZ=value; }
			get { return mZ; }
		}
#endif
			:
	}
}

コンストラクタでは、

・デフォルト値を用いたインスタンス作成
・初期値を指定したインスタンス作成
・他のベクトル値を初期値とするインスタンス作成

この 3 種類をとりあえず用意しておきます:
using System;
namespace ToyBox {
	public class Vector3 {
			:
		public Vector3() {
			X=Y=Z=0;
		}

		public Vector3(float inX,float inY,float inZ) {
			X=inX;
			Y=inY;
			Z=inZ;
		}

		public Vector3(Vector3 inSrc) {
			X=inSrc.X;
			Y=inSrc.Y;
			Z=inSrc.Z;
		}
			:
	}
}

初期化に関するものですが、すでに生成したインスタンスに対して 新たな値を上書き設定できた方が便利な時もあります。 併せて、初期化と同時に正規化したい場合もありますので、 その為のメソッドも用意しておきます:
using System;

namespace ToyBox {
	public class Vector3 {
			:
		public void Init(float inX,float inY,float inZ) {
			X=inX;
			Y=inY;
			Z=inZ;
		}

		public void InitWithNormalize(float inX,float inY,float inZ) {
			float invLen=(float)(1/Math.Sqrt(inX*inX+inY*inY+inZ*inZ));
			X=inX*invLen;
			Y=inY*invLen;
			Z=inZ*invLen;
		}
			:
	}
}

ベクトル型が備えていた方が望ましいプロパティを考えてみます。 第1に考えられるのはベクトルの長さではないでしょうか。 Vector3 クラスでは Length というプロパティでベクトルの長さ(ノルム)を 表現することとします。

実際の計算においてはベクトルの長さの逆数があると便利な場合もありますので、 Length の逆数を返す InvLength というプロパティも作っておきます:
using System; 

namespace ToyBox {
	public class Vector3 {
			:
		public float Length {
			get { return (float)Math.Sqrt(X*X+Y*Y+Z*Z); }
		}

		public float InvLength {
			get { return (float)(1.0/Math.Sqrt(X*X+Y*Y+Z*Z)); }
		}
			:
	}
}

3 次元のベクトル型に対する演算はいくつかあると思いますが、 ここでは基本的な演算を定義するに留めておきます。

すぐに思い浮かぶのが内積および外積ですが、これらは2つのベクトルに対する 演算であり、また、どちらが主・従であるという関係でもありません。 そんな訳で内積、外積に関しては Vector3 のクラスメソッドとして 実装しておきます。
using System;

namespace ToyBox {
	public class Vector3 {
			:
		// 内積
		static public float Dot(Vector3 inV1,Vector3 inV2) {
			return inV1.X*inV2.X+inV1.Y*inV2.Y+inV1.Z*inV2.Z;
		}

		// 外積
		static public Vector3 Cross(Vector3 inV1,Vector3 inV2) {
			return new Vector3(inV1.Y*inV2.Z-inV1.Z*inV2.Y,
							   inV1.Z*inV2.X-inV1.X*inV2.Z,
							   inV1.X*inV2.Y-inV1.Y*inV2.X);
		}
	}
}

もうひとつクラスメソッドとして、与えられたベクトルと同じ方向の 正規化したベクトルを返すメソッド UnitVector を定義してきます。 ベクトルそれ自身を正規化するメソッドも定義しますが、こちらは 戻り値 void の(インスタンス)メソッド Normalize として定義します:
using System;

namespace ToyBox {
	pulbic class Vector3 {
			:
		// 正規化したベクトルを返す。inV の値は変化しない。
		static public Vector3 UnitVector(Vector3 inV) {
			float s=inV.InvLength;
			return s*inV;
		}
			:	
		// 自分自身を正規化する
		public void Normalize() {
			float invLength=InvLength;
			X*=invLength;
			Y*=invLength;
			Z*=invLength;
		}
	}
}

後は + や - およびスカラ倍などの各種演算子と、 比較演算子の為の Equals や GetHashCode、ついでに ToString メソッドなども 実装します:
using System;

namespace ToyBox {
	public class Vector3 {
			:
		// 演算子オーバーロード等
		static public Vector3 operator+(Vector3 inVec) {
			return inVec;
		}

		static public Vector3 operator-(Vector3 inVec) {
			return new Vector3(-inVec.X,-inVec.Y,-inVec.Z);
		}

		static public bool operator==(Vector3 inV1,Vector3 inV2) {
			return inV1.X==inV2.X
				&& inV1.Y==inV2.Y
				&& inV1.Z==inV2.Z;
		}

		static public bool operator!=(Vector3 inV1,Vector3 inV2) {
			return !(inV1==inV2);
		}

		static public Vector3 operator*(float inS,Vector3 inV) {
			return new Vector3(inS*inV.X, inS*inV.Y, inS*inV.Z);
		}

		static public Vector3 operator*(Vector3 inV,float inS) {
			return inS*inV;
		}

		static public Vector3 operator/(Vector3 inV,float inS) {
			float invS=1.0f/inS;
			return new Vector3(inV.X*invS, inV.Y*invS, inV.Z*invS);
		}

		static public Vector3 operator+(Vector3 inV1,Vector3 inV2) {
			return new Vector3(inV1.X+inV2.X,
							   inV1.Y+inV2.Y,
							   inV1.Z+inV2.Z);
		}

		static public Vector3 operator-(Vector3 inV1,Vector3 inV2) {
			return new Vector3(inV1.X-inV2.X,
							   inV1.Y-inV2.Y,
							   inV1.Z-inV2.Z);
		}

		public override bool Equals(object inObj) {
			if(inObj==null
			|| inObj.GetType()!=GetType()) {
				return false;
			}
			Vector3 vec3=(Vector3)inObj;
			return this==vec3;
		}

		public override int GetHashCode() {
			return X.GetHashCode()
				 ^ Y.GetHashCode()
				 ^ Z.GetHashCode();
		}

		override public string ToString() {
			return "("+X+","+Y+","+Z+")";
		}
	}
}

その他、よく使うベクトル値-ゼロベクトル、X,Y,Z 軸方向の単位ベクトルを クラス変数として実装しておきます:
using System;

namespace ToyBox {
	public class Vector3 {
			:
		static Vector3 mZero=new Vector3(0,0,0);
		static public Vector3 Zero {
			get { return mZero; }
		}

		static Vector3 mXAxis=new Vector3(1,0,0);
		static public Vector3 XAxis {
			get { return mXAxis; }
		}

		static Vector3 mYAxis=new Vector3(0,1,0);
		static public Vector3 YAxis {
			get { return mYAxis; }
		}

		static Vector3 mZAxis=new Vector3(0,0,1);
		static public Vector3 ZAxis {
			get { return mZAxis; }
		}
			:
	}
}

まとめると Vector3 クラスは次のようになります。

[入力用プログラムリスト Vector3.pdf は こちら]
//#define PROPERTY_XYZ

using System;

namespace ToyBox {
	public class Vector3 {
		// 内積
		static public float Dot(Vector3 inV1,Vector3 inV2) {
			return inV1.X*inV2.X+inV1.Y*inV2.Y+inV1.Z*inV2.Z;
		}

		// 外積
		static public Vector3 Cross(Vector3 inV1,Vector3 inV2) {
			return new Vector3(inV1.Y*inV2.Z-inV1.Z*inV2.Y,
							   inV1.Z*inV2.X-inV1.X*inV2.Z,
							   inV1.X*inV2.Y-inV1.Y*inV2.X);
		}

		// 正規化したベクトルを返す。inV の値は変化しない。
		static public Vector3 UnitVector(Vector3 inV) {
			float s=inV.InvLength;
			return s*inV;
		}

		static Vector3 mZero=new Vector3(0,0,0);
		static public Vector3 Zero {
			get { return mZero; }
		}

		static Vector3 mXAxis=new Vector3(1,0,0);
		static public Vector3 XAxis {
			get { return mXAxis; }
		}

		static Vector3 mYAxis=new Vector3(0,1,0);
		static public Vector3 YAxis {
			get { return mYAxis; }
		}

		static Vector3 mZAxis=new Vector3(0,0,1);
		static public Vector3 ZAxis {
			get { return mZAxis; }
		}

#if !(PROPERTY_XYZ)
		public float X,Y,Z;
#else
		float mX,mY,mZ;
		public float X {
			set { mX=value; }
			get { return mX; }
		}
		public float Y {
			set { mY=value; }
			get { return mY; }
		}
		public float Z {
			set { mZ=value; }
			get { return mZ; }
		}
#endif

		public float Length {
			get { return (float)Math.Sqrt(X*X+Y*Y+Z*Z); }
		}

		public float InvLength {
			get { return (float)(1.0/Math.Sqrt(X*X+Y*Y+Z*Z)); }
		}

		public Vector3() {
			X=Y=Z=0;
		}

		public Vector3(float inX,float inY,float inZ) {
			X=inX;
			Y=inY;
			Z=inZ;
		}

		public Vector3(Vector3 inSrc) {
			X=inSrc.X;
			Y=inSrc.Y;
			Z=inSrc.Z;
		}

		public void Init(float inX,float inY,float inZ) {
			X=inX;
			Y=inY;
			Z=inZ;
		}

		public void InitWithNormalize(float inX,float inY,float inZ) {
			float invLen=(float)(1/Math.Sqrt(inX*inX+inY*inY+inZ*inZ));
			X=inX*invLen;
			Y=inY*invLen;
			Z=inZ*invLen;
		}

		// 自分自身を正規化する
		public void Normalize() {
			float invLength=InvLength;
			X*=invLength;
			Y*=invLength;
			Z*=invLength;
		}

		// 演算子オーバーロード等
		static public Vector3 operator+(Vector3 inVec) {
			return inVec;
		}

		static public Vector3 operator-(Vector3 inVec) {
			return new Vector3(-inVec.X,-inVec.Y,-inVec.Z);
		}

		static public bool operator==(Vector3 inV1,Vector3 inV2) {
			return inV1.X==inV2.X
				&& inV1.Y==inV2.Y
				&& inV1.Z==inV2.Z;
		}

		static public bool operator!=(Vector3 inV1,Vector3 inV2) {
			return !(inV1==inV2);
		}

		static public Vector3 operator*(float inS,Vector3 inV) {
			return new Vector3(inS*inV.X, inS*inV.Y, inS*inV.Z);
		}

		static public Vector3 operator*(Vector3 inV,float inS) {
			return inS*inV;
		}

		static public Vector3 operator/(Vector3 inV,float inS) {
			float invS=1.0f/inS;
			return new Vector3(inV.X*invS, inV.Y*invS, inV.Z*invS);
		}

		static public Vector3 operator+(Vector3 inV1,Vector3 inV2) {
			return new Vector3(inV1.X+inV2.X,
							   inV1.Y+inV2.Y,
							   inV1.Z+inV2.Z);
		}

		static public Vector3 operator-(Vector3 inV1,Vector3 inV2) {
			return new Vector3(inV1.X-inV2.X,
							   inV1.Y-inV2.Y,
							   inV1.Z-inV2.Z);
		}

		public override bool Equals(object inObj) {
			if(inObj==null
			|| inObj.GetType()!=GetType()) {
				return false;
			}
			Vector3 vec3=(Vector3)inObj;
			return this==vec3;
		}

		public override int GetHashCode() {
			return X.GetHashCode()
				 ^ Y.GetHashCode()
				 ^ Z.GetHashCode();
		}

		override public string ToString() {
			return "("+X+","+Y+","+Z+")";
		}
	}
}


Vector3 を使ったサンプルプログラムを以下に示します。
[入力用プログラムリスト Program003.pdf は こちら]
using System;
using System.Collections.Generic;
using System.Diagnostics;

using ToyBox;

namespace No003 {
	class Program003 {
		static void Main(string[] args) {
			Vector3 v1=new Vector3();
			Console.WriteLine("v1="+v1);

			Vector3 v2=new Vector3(1,2,3);
			Console.WriteLine("v1.Init(4,5,6)");
			v1.Init(4,5,6);
			Console.WriteLine("v1="+v1);
			Console.WriteLine("v2="+v2);
			Console.WriteLine("v1+v2="+(v1+v2));
			Console.WriteLine("v1・v2="+Vector3.Dot(v1,v2));
			Console.WriteLine("v1xV2="+Vector3.Cross(v1,v2));
			Console.WriteLine("2*v1="+2*v1);
			Console.WriteLine("normalize(v1)="+Vector3.UnitVector(v1));
			Console.WriteLine("v1="+v1);
			Console.WriteLine("v1.Normalize()");
			v1.Normalize();
			Console.WriteLine("v1="+v1);

			// PROPERTY_XYZ を define する/しない、で
			// 速度の違いを調べてみてください。
			Stopwatch sw=new Stopwatch();
			Vector3 vec3=new Vector3();
			Random random=new Random();

			sw.Start();
				float total=0;
				for(int i=0; i<100000000; i++) {
					float x=random.Next()/(float)int.MaxValue;
					float y=random.Next()/(float)int.MaxValue;
					float z=random.Next()/(float)int.MaxValue;
					vec3.Init(x,y,z);
					vec3.Normalize();
					total+=vec3.X+vec3.Y+vec3.Z;
				}
			sw.Stop();
			Console.WriteLine("total="+total);
			Console.WriteLine("time: "+sw.ElapsedTicks);
		}
	}
}