このOBBと、床や壁にあたる線(別に線分でも半直線でも直線でもよいが)
との衝突判定について書きます。
要はこんな感じの一般的な状況を考えて、長方形と線が交差しているかを考えます。 見つけるのは「当たってるかどうか?と、どこに交点があるか?」の2つにすることにします。
これら2つはいっぺんに出てきます。
①データの保持方法
取りあえず、OBBと直線をどうやって保持するかを考えることにします。
OBB(向きのある長方形)
class OBB
{
public:
OBB( const Vec& length, const Vec& s, const Vec& direction )
:length_(length / 2.0),s_(s),direction_(direction)
{
direction_.Normalize();//ベクトルの正規化
};
public:
Vec S() const{ return s_; }
Vec Direction() const{ return direction_; }
Vec Length()const{ return length_; }
private:
Vec direction_; //向き(0度でx方向) ただし正規化(つまり長さが1)されていることが保障されている
Vec s_;//中心位置
Vec length_; //各辺の長さの半分
};
つぎに線分
class Segment
{
public:
Segment( Vec s, Vec d )
:s_(s),d_(d)
{
}
public:
Vec S() const{ return s_; };
Vec D() const{ return d_; };
private:
Vec s_;//始点
Vec d_;//向き
};
②座標系の回転
さて実際に判定するわけですが、わざわざややこしいOBBを使う必要はありません。
このように座標系を移動⇒回転させて、向きのないしかも原点中心の長方形と判定させた方がいろいろと楽です。 これは意外と簡単で、次のようにすることにより実現します。
bool OBBToSegment( OBB& obb, const Segment& seg, Vec& s)
{
Vec obbDirectionX = obb.Direction();
Vec obbCenter = obb.S();
Vec obbLength = obb.Length();
Vec segSP = seg.S();
Vec segDirection = seg.D();
//OBB基準の座標系へ変換
segSP -= obbCenter; //線分の始点をOBBの中心座標分ずらす
segSP = Linear::VRotate2D( segSP, -obbDirectionX.y, obbDirectionX.x ); //OBBの角度分始点を回転
segDirection = Linear::VRotate2D( segDirection, -obbDirectionX.y, obbDirectionX.x ); //OBBの角度分線分の方向ベクトルを回転
・・・
► スポイラーを表示
今、obbの方向ベクトルは正規化されているつまり長さが1であることが保障されているので
そのx方向の長さがcosに、y方向の長さがsinになります。もちろんx軸と、OBBとの角度が30°なら-30°回転させるのでsinは符号が逆になります。
③スラブ
さて、平行四辺形は2組の平行な直線を用いて表すことができます。
この2組の平行な直線がそれぞれ90度に交わるときこれが長方形になる条件ということができます。
これらの直線のうち、1組の平行な2直線のことをスラブといいます。
とりあえず見てみましょう。 AABBを2つのスラブであらわしたとき、
1つひとつのスラブと判定したい線分との交点を求めることは極めて容易です。
double l = obbLength[i];
double t1 = (-l - segSP[i]) / segDirection[i];
double t2 = ( l - segSP[i]) / segDirection[i];
if( t1 > t2 ) std::swap( t1, t2 );
x方向のスラブも、y方向のスラブも↑の計算式で交点を求めることができ、
tの値は、それぞれ線分の長さのを何倍すれば交点までたどり着くかが代入され、
t1には、近い方の交点がt2には遠い方の交点が入ります。
これは三角形の相似を考えれば明らかなことです。
④判定
準備は整いました。
判定も極めて簡単です。
要は、当たっている場合はあるスラブの遠い方の交点があるスラブの近い方の交点よりも近い位置にあるということは起こりません。
一方当たっていない場合は、あるスラブの遠い方の交点(上の図ならty2)があるスラブの近い方の交点(同tx1)よりも近い距離にあります。
↑の図を見てください
つまりコードはこういうことになります。
double tmin = std::numeric_limits::min(); //線分、半直線なら0に
double tmax = std::numeric_limits::max(); //線分なら1に
for( int i = 1; i 1.0 )
{
return 0;
}
}
else
{
double t1 = (-l - segSP[i]) / segDirection[i];
double t2 = ( l - segSP[i]) / segDirection[i];
if( t1 > t2 ) std::swap( t1, t2 );
if( t1 > tmin ) tmin = t1;
if( t2 tmax ) return false;
}
}
bool OBBToSegment( OBB& obb, const Segment& seg, Vec& s)
{
Vec obbDirectionX = obb.Direction();
Vec obbCenter = obb.S();
Vec obbLength = obb.Length();
Vec segSP = seg.S();
Vec segDirection = seg.D();
//OBB基準の座標系へ変換
segSP -= obbCenter; //線分の始点をOBBの中心座標分ずらす
segSP = Linear::VRotate2D( segSP, -obbDirectionX.y, obbDirectionX.x ); //OBBの角度分始点を回転
segDirection = Linear::VRotate2D( segDirection, -obbDirectionX.y, obbDirectionX.x ); //OBBの角度分線分の方向ベクトルを回転
double tmin = 0.0;
double tmax = 1.0;
for( int i = 1; i 1.0 )
{
return 0;
}
}
else
{
double t1 = (-l - segSP[i]) / segDirection[i];
double t2 = ( l - segSP[i]) / segDirection[i];
if( t1 > t2 ) std::swap( t1, t2 );
if( t1 > tmin ) tmin = t1;
if( t2 tmax ) return false;
}
}
//交点
s = seg.S() + seg.D()*tmin;
return true;
}
こっちの方が一般性があるかなと思います。
ただし座標変換は厳密には必要なく、ベクトルに慣れていればOBBのままスラブと線分の交差判定をすることもできます。
またこのやり方は3Dへの応用も容易で、レイトレーシング法なんかにも使われたりするらしいです。
(具体的にはforループをi=1~2ではなく、i=1~3にすればそのまま3Dでつかえる)
・・・意外と説明むずい