OpenGLでOBB
うーん、OBB素晴しい…。
こんなきわどい衝突も検知できてしまいますね…。
個人的にハマったのは、回転行列を掛け合わせる順番を、
「描画」ではrotX*rotY*rotZとしていたのに、
「行列構造体」ではrotZ*rotX*rotYにしていたことですね。
(というか、それはOBBと関係ないよ…(´∀`;
仕様
白オブジェクトのみ操作できます。
緑オブジェクトに衝突すると、赤くなります。
操作方法
オブジェクトの移動
- Z・Xキー:X方向への移動
- A・Sキー:Y方向への移動
- Q・Wキー:Z方向への移動
- C・Vキー:X方向への回転
- D・Fキー:Y方向への回転
- E・Rキー:Z方向への回転
カメラの移動
- ↑↓キー:X軸回転
- ←→キー:Y軸回転
ソースコード
ちょい長めです(´Д`;
#include <math.h> #include <string> #include <iostream> #include <glut.h> #include <ctype.h> using namespace std; const float MTXLIB_PI = 3.14159265f; /** * 3次元ベクトル構造体 */ struct Vec3 { float x, y, z; Vec3(float x=0, float y=0, float z=0){this->x=x; this->y=y; this->z=z;} Vec3 operator +(Vec3 &vec) {return Vec3(this->x+vec.x, this->y+vec.y, this->z+vec.z);} Vec3 operator -(Vec3 &vec) {return Vec3(this->x-vec.x, this->y-vec.y, this->z-vec.z);} Vec3 operator *(float f) {return Vec3(this->x*f, this->y*f, this->z*f);} Vec3 operator /(float f) {return Vec3(this->x/f, this->y/f, this->z/f);} Vec3 operator =(Vec3 &vec) {return Vec3(this->x=vec.x, this->y=vec.y, this->z=vec.z);} Vec3 operator ==(Vec3 &vec) {return (this->x==vec.x && this->y==vec.y && this->z==vec.z);} Vec3 operator !=(Vec3 &vec) {return (this->x!=vec.x || this->y!=vec.y || this->z!=vec.z);} void Set(float x, float y, float z) {this->x=x; this->y=y; this->z=z;} // スカラの二乗を求める float LengthSq() {return x*x + y*y + z*z;} // スカラ float Length() {return (float)sqrt(LengthSq());} // 正規化 Vec3 Normalize() { float m = LengthSq(); if (m > 0.0f) m = 1.0f / m; else m = 0.0f; return Vec3(x*m, y*m, z*m); } // 内積 float Dot(Vec3 &vec) { return (this->x*vec.x + this->y*vec.y + this->z*vec.z); } // 外積 Vec3 Cross(Vec3 &vec) { return Vec3( this->y*vec.z - this->z*vec.y, this->z*vec.x - this->x*vec.z, this->x*vec.y - this->y*vec.x); } string Debug() { char tmp[256]; sprintf(tmp, "(x,y,z)=(%f,%f,%f", x, y, z); string ret = tmp; return ret; } }; /** * 4次元ベクトル構造体 */ struct Vec4 { float x, y, z, w; Vec4(float x=0, float y=0, float z=0, float w=0){this->x=x; this->y=y; this->z=z; this->w=w;} Vec4 operator +(Vec4 &vec) {return Vec4(this->x+vec.x, this->y+vec.y, this->z+vec.z, this->w+vec.w);} Vec4 operator -(Vec4 &vec) {return Vec4(this->x-vec.x, this->y-vec.y, this->z-vec.z, this->w-vec.w);} Vec4 operator *(float f) {return Vec4(this->x*f, this->y*f, this->z*f, this->w*f);} Vec4 operator /(float f) {return Vec4(this->x/f, this->y/f, this->z/f, this->w/f);} Vec4 operator =(Vec4 &vec) {return Vec4(this->x=vec.x, this->y=vec.y, this->z=vec.z, this->w=vec.w);} Vec4 operator ==(Vec4 &vec) {return (this->x==vec.x && this->y==vec.y && this->z==vec.z && this->w==vec.w);} Vec4 operator !=(Vec4 &vec) {return (this->x!=vec.x || this->y!=vec.y || this->z!=vec.z || this->w!=vec.w);} void Set(float x, float y, float z, float w) {this->x=x; this->y=y; this->z=z; this->w=w;} void Set(int i, float param) { if(i == 0) x = param; else if(i == 1) y = param; else if(i == 2) z = param; else if(i == 3) w = param; } float Get(int i) { if(i == 0) return x; else if(i == 1) return y; else if(i == 2) return z; else if(i == 3) return w; else return 0; } // スカラの二乗を求める float LengthSq() {return x*x + y*y + z*z + w*w;} // スカラ float Length() {return sqrt(LengthSq());} // 正規化 Vec4 Normalize() { float m = LengthSq(); if (m > 0.0f) m = 1.0f / m; else m = 0.0f; return Vec4(x*=m, y*=m, z*=m, w*=m); } // 内積 float Dot(Vec4 &vec) { return (this->x*vec.x + this->y*vec.y + this->z*vec.z + this->w*vec.w); } }; /** * 4×4行列構造体 */ struct Mat44 { Vec4 col[4]; Mat44(Vec4 v0=Vec4(1,0,0,0), Vec4 v1=Vec4(0,1,0,0), Vec4 v2=Vec4(0,0,1,0), Vec4 v3=Vec4(0,0,0,1)) { col[0] = v0; col[1] = v1; col[2] = v2; col[3] = v3; } Mat44 operator *(Mat44 &m) { Mat44 t; for (int r = 0; r < 4; r++) { for (int c = 0; c < 4; c++) { float f = 0; f += (Get(0, r) * m.Get(c, 0)); f += (Get(1, r) * m.Get(c, 1)); f += (Get(2, r) * m.Get(c, 2)); f += (Get(3, r) * m.Get(c, 3)); t.Set(c, r, f); } } return t; } Vec3 operator *(Vec3 &v) { Vec3 ret; ret.x = (v.x * this->col[0].x) + (v.y * this->col[1].x) + (v.z * this->col[2].x) + (this->col[3].x); ret.y = (v.x * this->col[0].y) + (v.y * this->col[1].y) + (v.z * this->col[2].y) + (this->col[3].y); ret.z = (v.x * this->col[0].z) + (v.y * this->col[1].z) + (v.z * this->col[2].z) + (this->col[3].z); return ret; } Vec4 operator *(Vec4 &v) { Vec4 ret; ret.x = v.x * this->col[0].x + v.y * this->col[1].x + v.z * this->col[2].x + v.w * this->col[3].x; ret.y = v.x * this->col[0].y + v.y * this->col[1].y + v.z * this->col[2].y + v.w * this->col[3].y; ret.z = v.x * this->col[0].z + v.y * this->col[1].z + v.z * this->col[2].z + v.w * this->col[3].z; ret.w = v.x * this->col[0].w + v.y * this->col[1].w + v.z * this->col[2].w + v.w * this->col[3].w; return ret; } float Get(int c, int r) {return col[c].Get(r);} void Set(int c, int r, float i) {col[c].Set(r, i);} // 単位行列 Mat44 Identity() { return Mat44(Vec4(1,0,0,0), Vec4(0,1,0,0), Vec4(0,0,1,0), Vec4(0,0,0,1)); } // 平行移動に変換 Mat44 Translate(float x, float y, float z) { Mat44 ret; ret = ret.Identity(); ret.col[3].x = x; ret.col[3].y = y; ret.col[3].z = z; return ret; } Mat44 Translate(Vec3 v) { Mat44 ret; ret = ret.Identity(); ret.col[3].x = v.x; ret.col[3].y = v.y; ret.col[3].z = v.z; return ret; } // 回転行列に変換 Mat44 Rotate(char axis, float rad) { Mat44 ret; float sinA = sinf(rad); float cosA = cosf(rad); switch (axis) { case 'x': case 'X': ret.col[0].x = 1.0F; ret.col[1].x = 0.0F; ret.col[2].x = 0.0F; ret.col[0].y = 0.0F; ret.col[1].y = cosA; ret.col[2].y = -sinA; ret.col[0].z = 0.0F; ret.col[1].z = sinA; ret.col[2].z = cosA; break; case 'y': case 'Y': ret.col[0].x = cosA; ret.col[1].x = 0.0F; ret.col[2].x = sinA; ret.col[0].y = 0.0F; ret.col[1].y = 1.0F; ret.col[2].y = 0.0F; ret.col[0].z = -sinA; ret.col[1].z = 0.0F; ret.col[2].z = cosA; break; case 'z': case 'Z': ret.col[0].x = cosA; ret.col[1].x = -sinA; ret.col[2].x = 0.0F; ret.col[0].y = sinA; ret.col[1].y = cosA; ret.col[2].y = 0.0F; ret.col[0].z = 0.0F; ret.col[1].z = 0.0F; ret.col[2].z = 1.0F; break; } ret.col[0].w = 0.0F; ret.col[1].w = 0.0F; ret.col[2].w = 0.0F; ret.col[3].x = 0.0F; ret.col[3].y = 0.0F; ret.col[3].z = 0.0F; ret.col[3].w = 1.0F; return ret; } // YawPitchRollを指定して回転行列を取得 Mat44 YawPitchRoll(float y, float x, float z) { Mat44 ret; Mat44 mY = ret.Rotate('y', y); Mat44 mX = ret.Rotate('x', x); Mat44 mZ = ret.Rotate('z', z); return mZ*mX*mY; } // 拡大・縮小行列に変換 Mat44 Scale(float x, float y, float z) { Mat44 ret; ret = ret.Identity(); ret.col[0].x = x; ret.col[1].y = y; ret.col[2].z = z; return ret; } }; // 角度をラジアンに変換 float DegToRad(float deg) { return deg*MTXLIB_PI/180; } /** * 箱構造体 */ struct TCube { Vec3 pos; // 中心座標 Vec3 radius; // 半径 Vec3 rot; // 回転角度 Vec3 axisX; // 分離軸X Vec3 axisY; // 分離軸Y Vec3 axisZ; // 分離軸Z Vec3 GetMinVec3() { return Vec3( pos.x-radius.x, pos.y-radius.y, pos.z-radius.z); } Vec3 GetMaxVec3() { return Vec3( pos.x+radius.x, pos.y+radius.y, pos.z+radius.z); } // 分離軸を更新 void UpdateAxisAll() { Mat44 mRot = Mat44().YawPitchRoll(DegToRad(rot.y), DegToRad(rot.x), DegToRad(rot.z)); axisX = mRot*Vec3(1, 0, 0); axisY = mRot*Vec3(0, 1, 0); axisZ = mRot*Vec3(0, 0, 1); } }; bool CompareLengthOBB(TCube&, TCube&, Vec3, Vec3); /** * 境界箱(OBB)による当たり判定 * @return 衝突していたらtrue */ bool IsCollideBoxOBB(TCube cA, TCube cB) { // 中心間の距離ベクトル算出 Vec3 vDistance = cB.pos - cA.pos; // 分離軸更新 cA.UpdateAxisAll(); cB.UpdateAxisAll(); // 分離軸を比較 if(!CompareLengthOBB(cA, cB, cA.axisX, vDistance)) return false; if(!CompareLengthOBB(cA, cB, cA.axisY, vDistance)) return false; if(!CompareLengthOBB(cA, cB, cA.axisZ, vDistance)) return false; if(!CompareLengthOBB(cA, cB, cB.axisX, vDistance)) return false; if(!CompareLengthOBB(cA, cB, cB.axisY, vDistance)) return false; if(!CompareLengthOBB(cA, cB, cB.axisZ, vDistance)) return false; // 分離軸同士の外積を比較 Vec3 vSep; vSep = cA.axisX.Cross(cB.axisX); if(!CompareLengthOBB(cA, cB, vSep, vDistance)) return false; vSep = cA.axisX.Cross(cB.axisY); if(!CompareLengthOBB(cA, cB, vSep, vDistance)) return false; vSep = cA.axisX.Cross(cB.axisZ); if(!CompareLengthOBB(cA, cB, vSep, vDistance)) return false; vSep = cA.axisY.Cross(cB.axisX); if(!CompareLengthOBB(cA, cB, vSep, vDistance)) return false; vSep = cA.axisY.Cross(cB.axisY); if(!CompareLengthOBB(cA, cB, vSep, vDistance)) return false; vSep = cA.axisY.Cross(cB.axisZ); if(!CompareLengthOBB(cA, cB, vSep, vDistance)) return false; vSep = cA.axisZ.Cross(cB.axisX); if(!CompareLengthOBB(cA, cB, vSep, vDistance)) return false; vSep = cA.axisZ.Cross(cB.axisY); if(!CompareLengthOBB(cA, cB, vSep, vDistance)) return false; vSep = cA.axisZ.Cross(cB.axisZ); if(!CompareLengthOBB(cA, cB, vSep, vDistance)) return false; return true; } /** * OBBの投影距離比較 * @return 衝突していたらtrue */ bool CompareLengthOBB(TCube &cA, TCube &cB, Vec3 vSep, Vec3 vDistance) { // 分離軸上のAからBの距離 float length = fabsf(vSep.Dot(vDistance)); // 分離軸上でAから最も遠いAの頂点までの距離 float lenA = fabsf(cA.axisX.Dot(vSep)*cA.radius.x) + fabsf(cA.axisY.Dot(vSep)*cA.radius.y) + fabsf(cA.axisZ.Dot(vSep)*cA.radius.z); // 分離軸上でBから最も遠いBの頂点までの距離 float lenB = fabsf(cB.axisX.Dot(vSep)*cB.radius.x) + fabsf(cB.axisY.Dot(vSep)*cB.radius.y) + fabsf(cB.axisZ.Dot(vSep)*cB.radius.z); if(length > lenA + lenB) { return false; } return true; } /** * 箱の描画 */ void DrawCube(Vec3 pos, Vec3 radius, Vec3 rot, int color) { // シーンの描画 static GLfloat red[] = { 0.8, 0.2, 0.2, 1.0 }; static GLfloat gleen[] = { 0.2, 0.8, 0.2, 1.0 }; static GLfloat white[] = { 0.8, 0.8, 0.8, 1.0 }; GLfloat *c; if(color == 0) c = red; else if(color == 1) c = gleen; else c = white; // 陰影付けをONにする glEnable(GL_LIGHTING); // 箱描画 glPushMatrix(); { glTranslatef(pos.x, pos.y, pos.z); glRotatef(rot.z, 0, 0, 1); glRotatef(rot.x, 1, 0, 0); glRotatef(rot.y, 0, 1, 0); glScaled(radius.x*2, radius.y*2, radius.z*2); glMaterialfv(GL_FRONT, GL_DIFFUSE, c); glutSolidCube(1); } glPopMatrix(); // 陰影付けをOFFにする glDisable(GL_LIGHTING); } TCube self; // 箱1(コイツが動く) TCube target; // 箱2(コイツは動かない) Vec3 vEye; // カメラ座標 bool bHit; // 当たりフラグ /** * 初期化 */ void Init(void) { // 初期設定 glClearColor(0.0, 0.0, 1.0, 0.0); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glEnable(GL_LIGHT0); // 箱を作る self.pos = Vec3(1, 0, 0); self.radius = Vec3(0.4f, 0.5f, 0.6f); self.rot = Vec3(0, 0, 0); target.pos = Vec3(0, 0, 0); target.radius = Vec3(0.5f, 0.6f, 0.4f); target.rot = Vec3(30, 45, 60); // カメラ座標初期化 vEye = Vec3(0, 0, -5); bHit = false; } /** * キーが押されたときの処理 * @param key 押下キー * @param x, y 座標 */ void InputKey(unsigned char key, int x, int y) { // 動かす float mov = 0.1f; float rot = 1; Mat44 mRot; switch(toupper(key)) { case 'Z': self.pos.x += mov; break; case 'X': self.pos.x -= mov; break; case 'A': self.pos.y += mov; break; case 'S': self.pos.y -= mov; break; case 'Q': self.pos.z += mov; break; case 'W': self.pos.z -= mov; break; case 'C': self.rot.x -= rot; break; case 'V': self.rot.x += rot; break; case 'D': self.rot.y -= rot; break; case 'F': self.rot.y += rot; break; case 'E': self.rot.z -= rot; break; case 'R': self.rot.z += rot; break; case 0x1b: // ESCキー // プログラムを終了 exit(0); break; } if(self.rot.x < 0) self.rot.x += 360; if(self.rot.x > 360) self.rot.x -= 360; if(self.rot.y < 0) self.rot.y += 360; if(self.rot.y > 360) self.rot.y -= 360; if(self.rot.z < 0) self.rot.z += 360; if(self.rot.z > 360) self.rot.z -= 360; // 当たり判定 if(IsCollideBoxOBB(self, target)) { bHit = true; } else { bHit = false; } } /** * 特殊キーが押されたときの処理 * @param key 押下キー * @param x, y 座標 */ void InputKeySp(int key, int x, int y) { // カメラ移動 Vec3 vAxis; Vec3 vN; switch(key) { case GLUT_KEY_UP: // 上キー vAxis = Vec3(-1, 0, 0); break; case GLUT_KEY_DOWN: // 下キー vAxis = Vec3(1, 0, 0); break; case GLUT_KEY_LEFT: // 左キー vAxis = Vec3(0, 1, 0); break; case GLUT_KEY_RIGHT: // 右キー vAxis = Vec3(0, -1, 0); break; } vN = vEye.Normalize().Cross(vAxis); vEye = vEye + vN; } /** * 描画 */ void Display(void) { // 画面クリア glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 視点の移動 glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(vEye.x, vEye.y, vEye.z, 0, 0, 0, 0, 1, 0); // 箱の描画 if(bHit) DrawCube(self.pos, self.radius, self.rot, 0); else DrawCube(self.pos, self.radius, self.rot, 2); DrawCube(target.pos, target.radius, target.rot, 1); // 地面を線画で描く glColor3d(0.0, 0.0, 0.0); glBegin(GL_LINES); { for (int i = -10; i <= 10; i++) { glVertex3d((GLdouble)i, -0.5, -10.0); glVertex3d((GLdouble)i, -0.5, 10.0); glVertex3d(-10.0, -0.5, (GLdouble)i); glVertex3d( 10.0, -0.5, (GLdouble)i); } } glEnd(); glutSwapBuffers(); } /** * ウィンドウのリサイズ */ void Resize(int w, int h) { // ウィンドウ全体をビューポートにする glViewport(0, 0, w, h); // 透視変換行列を設定する glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(30.0, (double)w / (double)h, 1.0, 100.0); // モデルビュー変換行列を指定しておく glMatrixMode(GL_MODELVIEW); } /** * アイドル時 */ void Idle() { glutPostRedisplay(); } /** * メイン関数 */ int main(int argc, char *argv[]) { glutInit(&argc, argv); glutInitWindowSize(640, 480); glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE); glutCreateWindow(argv[0]); glutDisplayFunc(Display); glutReshapeFunc(Resize); // キーボード入力用関数を登録 glutKeyboardFunc(InputKey); glutSpecialFunc(InputKeySp); glutIdleFunc(Idle); Init(); // メインループ glutMainLoop(); return 0; }