3D衝突編
その0 3D基本プリミティブの型定義
衝突判定をする上で「基本プリミティブ」を定義すると実装が楽になります。この章ではそんなプリミティブを定義してみます。下記のプリミティブの具体的な実装はprimitive.hとして公開致します(^-^)。プリミティブは沢山考えられるため、必要に応じて定義も増やしていきます:
○ 浮動小数点誤差
衝突判定では浮動小数点の誤差が問題になります。数学的には0のはずなのに計算誤差で0.000001みたいな小さな誤差が出て衝突していないと判定される事があります。これによりプルプル震えたりすり抜けたりと割と散々なのです。その為「このくらい小さい値になったら0とみなそう」という範囲を決めると安定します:
#define _OX_EPSILON_ 0.000001f // 誤差
イプシロン(ε:epsilon)は科学系の文献で誤差として良く利用されるギリシア文字です。
○ 3次元ベクトル(Float3)
点やベクトルなどを表す基本中の基本の型になります。欲しい機能としては加算、減算、スカラー(係数)との掛け算、割り算はまぁ必須として、ベクトルとしてとらえた時の長さ(length)、べき乗長さ(lengthSq)、後は内積と外積と正規化はしょっちゅう使いますので機能に加えたい所です:
struct Float3 {
float x, y, z;
Float3();
Float3( float x, float y, float z );
~Float3();
Float3 operator +( const Float3 &r ) const;
Float3 operator -( const Float3 &r ) const;
Float3 operator -() const;
Float3 operator *( const Float3 &r ) const;
Float3 operator /( const Float3 &r ) const;
Float3 operator *( float r ) const;
Float3 operator /( float r ) const;
friend Float3 operator *( float l, const Float3 &r );
friend Float3 operator /( float l, const Float3 &r );
float dot( const Float3 &r ) const;
Float3 cross( const Float3 &r ) const;
float length() const;
float lengthSq() const;
void norm();
Float3 getNorm() const;
};
getNormメソッドは非破壊に標準化したFloat3を取得します。「正規化値が欲しいけど成分そのままで…」というケースは結構あります。
○ 点(Point)とベクトル(Vec3)
点はFloat3型として扱った方が良いので、typedefで再定義するくらいで良いと思います。一方ベクトルはFloat3の機能を踏襲しつつ他方のベクトルと垂直・平行かどうかの判定くらいの機能はあって良いかなと思いますのでFloat3を継承します:
// 点
typedef Float3 Point;
// 3次元ベクトル
struct Vec3 : public Float3 {
Vec3() {}
Vec3( float x, float y, float z )
Vec3( const Float3 &r ) : Float3( r )
~Vec3()
Vec3& operator =( const Float3 &r );
// 垂直関係にある?
bool isVertical( const Float3 &r ) const;
// 平行関係にある?
bool isParallel( const Vec3 &r ) const;
}
Vec3はFloat3を継承していますが、Float3(Point)で値を代入したい事もありますので、コピーコンストラクタと代入演算子を定義しています。
○ 直線(Line)
直線(無限に伸びた線)は「直線上の一点と方向ベクトル」で定義できます。点上の座標を取得する機能はあって良いかなと思います:
// 直線
struct Line {
Point p;
Vec3 v;
Line();
Line( const Point &p, const Vec3 &v );
// 点上の座標を取得
// t: ベクトルに掛け算する係数
Float3 getPoint( float t ) const;
};
機能は小さいものなら追加で加えても良いと思いますが、とりあえず値定義だけで今は良いかなと。
○ 線分(Segment)
線分は2つの点を結ぶ線ですが、レイ(方向性を持った線分)などでも使えるようにするために始点とベクトルで定義しておきます。よって終点座標を算出する機能を持たせます:
// 線分
struct Segment : public Line {
Segment();
Segment( const Point &p, const Vec3 &v );
Segment( const Point &p1, const Point &p2 );
// 終点を取得
Float3 getEndPoint() const;
};
○ 球(Sphere)
球は中心点と半径のみで定義できる軽量なプリミティブです。対称性が極めて高いため衝突判定が簡単になるのが嬉しいプリミティブです:
// 球
struct Sphere {
Point p;
float r; // 半径
Sphere() {}
Sphere( const Point &p, float r ) : p( p ), r( r ) {}
~Sphere() {}
};
○ カプセル(Capsule)
カプセルは線分上を半径rの球がスウィープした図形です。定義としては線分に半径が追加された物になります:
// カプセル
struct Capsule {
Segment s;
float r; // 半径
Capsule() : r( 0.5f ) {}
Capsule( const Segment &s, float r ) : s( s ), r( r ) {}
Capsule( const Point &p1, const Point &p2, float r ) : s( p1, p2 ), r( r ) {}
~Capsule() {}
};