TankAction
私が所属しているプログラミングサークル(MCC)では毎年学祭で来場者の小学生向けにゲームを作って遊んでもらっています. 今回はOpenGLとOpenCVの練習もかねて,3D対戦シューティングゲームを作成しました. オブジェクト指向の流儀に則って開発したため拡張が簡単に出来ます(けど急ピッチで開発したためアクセス指定子はいい加減です).
自機の手動操作と自律操作(敵機と同じアルゴリズムで勝手に戦ってくれます)を動的に切り替えたり,敵の数を動的に増やしたり減らしたり出来る機能が特徴的です. また,OpenCVの顔認識機能とWebカメラを使った(擬似的な)ヘッドトラッキングにも対応しています.
Contents |
ダウンロード
Windows/Mac用の実行ファイル,ソースコード,ライブラリなどが同梱されています.
- TankAction.zip
- HeadTrackingSample.zip
- 足りないファイルは TankAction.zip から補完してください
実行ファイルは64bit環境向けにビルドされているので(恐らく)32bit環境では動きません.
ヘッダを適切に書き換えて,OpenCVでHaar-Like特徴量を使った顔認識をするための haarcascade_frontalface_alt.xml (OpenCVに同梱されているサンプルデータ)へのパスを適切に設定すればOSを問わずWindowsやMac,Linuxなどでコンパイル出来るはずです. Visual Studio 2010とXcode 4.0で動作確認をしました. 「OpenGLを動かせる環境はあるけど,OpenCVを新たに入れるのは面倒」という場合は main.cpp の HEADTRACKING_MODE を0にすればコンパイル出来ます.
操作方法
↑ | 前進 |
↓ | 後退 |
← | 左旋回 |
→ | 右旋回 |
Space | 射撃 |
v | 敵の数を増やす |
c | 敵の数を減らす |
m | 自機の手動操作・自律操作を切り替える(デフォルトは自律操作) |
r | ゲームリセット |
s | ゲーム一時停止 |
Escape | ゲーム終了 |
x | [デバッグ用]ステージのレベルアップ |
z | [デバッグ用]ステージのレベルダウン |
ステージがレベルアップするにつれて弾丸の発射方向と頻度が増えたり敵が強くなったりします.
スクリーンショット
ソースコード
TankAction!
全てのモデルはGLObjectクラスを継承していて,さらにPlayerTankとEnemyTankはTankクラスを継承しています.
main.cpp
#include <iostream> #include <cstdlib> #include <cmath> #include <ctime> #include <string> #include <vector> #include <queue> using namespace std; // #include <GL/glut.h> // 環境によって変える #include "./glut.h" //#include <GLUT/glut.h> // for Mac #pragma comment(lib, "./glut32.lib") #define PI 3.141592 #define FULLSCREEN_MODE 0 // 0ならウィンドウ,1ならフルスクリーンでマウス非表示 #define HEADTRACKING_MODE 0 // OpenCVを使ったヘッドトラッキングを有効にするかどうか #define WINDOW_WIDTH 640 #define WINDOW_HEIGHT 480 #define MAX_BALL 10000 // 画面上に表示する最大の玉の数 #define PLAYER 0 // 自機を表すtype #define ENEMY 1 // 敵を表すtype int AUTOPLAY = 1; int key_up = 0, key_down = 0, key_left = 0, key_right = 0, key_space = 0; // キー状態を記録 int global_time; // 内部時計 int list; // 文字列表示用 int enemy_level; // 敵の強さのレベル int is_game_start; // ゲーム開始・終了用 int sudden_stop; // 一時停止用 int enemySize = 1; // 最初の敵の数 #include "GLColor.h" #include "PlayerTank.h" #include "EnemyTank.h" #include "Ball.h" #include "Field.h" #include "Parameter.h" EnemyTank autoPlayer; // 自機 PlayerTank player; // AUTOPLAYモードの動的切り替えのために2つ用意 //EnemyTank enemyPool[MAX_ENEMY]; // 敵 vector<EnemyTank> enemyPool; Field field; Parameter parameter; #if HEADTRACKING_MODE #include <opencv2/objdetect/objdetect.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #define IMG_SCALE 2 // 1だと重すぎる,4だとちょっと離れたら認識しなくなる using namespace cv; queue<pair<float, pair<float, float> > > peekQue; // peek用キュー <radius, <x, y>> float faceX = 0, faceY = 0, faceRadius = 1.0; float myFaceX = WINDOW_WIDTH/2, myFaceY = WINDOW_HEIGHT/2 + 100; // path to haarcascade_frontalface_alt.xml String cascadeName = "/Users/usi3/Desktop/OpenCV-2.2.0/data/haarcascades/haarcascade_frontalface_alt.xml"; CvCapture* capture = 0; Mat frame, frameCopy, image; CascadeClassifier cascade; #endif // ゲーム開始時にだけ必要な初期化を行う void gameInit(void){ srand(time(NULL)); enemy_level = -1; is_game_start = 1; sudden_stop = 0; } // レベルアップ時にだけ必要な初期化を行う void nextStageInit(void){ enemy_level++; global_time = 0; autoPlayer.reset(PLAYER); player.reset(PLAYER); autoPlayer.maxLife *= enemySize; autoPlayer.life = player.maxLife = player.life = autoPlayer.maxLife; enemyPool.clear(); for (int i=0; i<enemySize; i++) { // 勝手にresetされるはず enemyPool.push_back(*(new EnemyTank)); } /* for (int i=0; i<enemySize; i++) { enemyPool[i].reset(); }*/ } // キー操作 void keyDown(unsigned char key, int x, int y){ switch(key){ case ' ': key_space = 1; break; case '\33': if(FULLSCREEN_MODE) glutLeaveGameMode(); exit(0); break; case 'x': enemy_level++; break; case 'z': enemy_level--; break; case 'v': enemySize++; enemyPool.push_back(*(new EnemyTank)); break; case 'c': if(enemyPool.size() > 1){ enemySize--; enemyPool.pop_back(); } break; case 'r': is_game_start = 1; gameInit(); nextStageInit(); break; case 's': sudden_stop = sudden_stop ? 0 : 1; break; case 'm': if(AUTOPLAY){ // playerにスイッチ AUTOPLAY = 0; player.setStatus(autoPlayer); autoPlayer.isExist = false; player.isExist = true; }else{ // autoPlayerにスイッチ AUTOPLAY = 1; autoPlayer.setStatus(player); autoPlayer.isExist = true; player.isExist = false; } break; default: break; } } void keyUp(unsigned char key, int x, int y){ switch(key){ case ' ': key_space = 0; break; default: break; } } void specialKeyDown(int key, int x, int y){ switch(key){ case GLUT_KEY_UP: key_up = 1; break; case GLUT_KEY_DOWN: key_down = 1; break; case GLUT_KEY_LEFT: key_left = 1; break; case GLUT_KEY_RIGHT: key_right = 1; break; default: break; } } void specialKeyUp(int key, int x, int y){ switch(key){ case GLUT_KEY_UP: key_up = 0; break; case GLUT_KEY_DOWN: key_down = 0; break; case GLUT_KEY_LEFT: key_left = 0; break; case GLUT_KEY_RIGHT: key_right = 0; break; default: break; } } // 表示制御 void display(void){ if(sudden_stop) return; if(is_game_start){ glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); #if HEADTRACKING_MODE if (!peekQue.empty()) { pair<float, pair<float, float> > faceInfo = peekQue.back(); peekQue.pop(); faceX += (faceInfo.second.first/12 - faceX)*0.1; faceY += (faceInfo.second.second/6 - faceY)*0.1; faceRadius += ((faceInfo.first-30)/50 - faceRadius)*0.1; //cout << "faceY = " << faceY << endl; //cout << "faceRadius = " << faceRadius << endl; } // 視点変更 glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(32.0, (double)WINDOW_WIDTH / (double)WINDOW_HEIGHT, 1.0, 100.0); glTranslated(0.0, 0.0, 0.0); gluLookAt(0.0, 45.0, 35.0, faceX, 0.0, -faceY, 0.0, 1.0, 0.0); #endif // オブジェクト描画 glMatrixMode(GL_MODELVIEW); glLoadIdentity(); #if HEADTRACKING_MODE glScaled(faceRadius, faceRadius, faceRadius); #endif // フィールド field.display(); // 敵 int enemyLife = 0; for (int i=0; i<enemyPool.size(); i++) { enemyPool[i].display(); enemyLife += enemyPool[i].life; } if(AUTOPLAY){ // 自機 autoPlayer.display(); // パラメーター表示 parameter.display(autoPlayer.life, enemyLife); }else{ player.display(); parameter.display(player.life, enemyLife); } // ダブルバッファリング glutSwapBuffers(); } return; } // 画面 and 視点制御 void reshape(GLsizei w, GLsizei h){ // 画面の大きさを変更する //cout << "reshape w = " << w << "h = " << h << endl; glutReshapeWindow(WINDOW_WIDTH, WINDOW_HEIGHT); glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(32.0, (double)w / (double)h, 1.0, 100.0); glTranslated(0.0, 0.0, 0.0); gluLookAt(0.0, 45.0, 35.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); } #if HEADTRACKING_MODE void cvInit(){ if(!cascade.load(cascadeName)){ cerr << "ERROR: Could not load classifier cascade" << endl; return; } capture = cvCaptureFromCAM(0); if(!capture){ cerr << "Capture from CAM didn't work" << endl; return; } cvNamedWindow("result", 1); } void detectAndDraw(Mat& img, CascadeClassifier& cascade){ vector<Rect> faces; // CV_8UC1: 1チャネル8ビットunsigned char Mat gray, smallImg(cvRound(img.rows/IMG_SCALE), cvRound(img.cols/IMG_SCALE), CV_8UC1); // 色空間をカラーからグレースケールへ変換 cvtColor(img, gray, CV_BGR2GRAY); // IMG_SCALEに応じて小さくする resize(gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR); equalizeHist(smallImg, smallImg); double t = (double)cvGetTickCount(); cascade.detectMultiScale( smallImg, faces, 1.1, 2, 0 //|CV_HAAR_FIND_BIGGEST_OBJECT //|CV_HAAR_DO_ROUGH_SEARCH |CV_HAAR_SCALE_IMAGE , Size(30, 30) ); t = (double)cvGetTickCount() - t; //printf("detection time = %g ms\n", t/((double)cvGetTickFrequency()*1000.)); // 1つの顔にしか対応しないことにする vector<Rect>::const_iterator r = faces.begin(); // 顔が1つ以上検出されているなら最初の顔のみについて処理 if (r != faces.end()) { Point center; center.x = cvRound((r->x + r->width*0.5)*IMG_SCALE); center.y = cvRound((r->y + r->height*0.5)*IMG_SCALE); //cout << center.x << " " << center.y << endl; // ディスプレイを正面から見たとき顔は下の方に映るからyを半分引いてない. float radius = cvRound((r->width + r->height)*0.25*IMG_SCALE); circle( img, center, radius, CV_RGB(0,0,255), 3, 8, 0 ); // radius 34 - 190 くらい peekQue.push(make_pair(radius, make_pair(center.x-myFaceX, center.y-myFaceY))); } imshow("result", img); } void cvTimer(){ // for debug //if (rand()%100 < 10) { //int x = cos(global_time/5)*320; //int y = sin(global_time/5)*320; //peekQue.push(make_pair(x, y)); //} IplImage* iplImg = cvQueryFrame(capture); frame = iplImg; if(frame.empty()){ return; } if(iplImg->origin == IPL_ORIGIN_TL){ frame.copyTo(frameCopy); }else{ flip(frame, frameCopy, 0); } detectAndDraw(frameCopy, cascade); } #endif // メインループ void timer(int value){ glutPostRedisplay(); glutTimerFunc(50, timer, 0); if(sudden_stop) return; #if HEADTRACKING_MODE cvTimer(); #endif if(is_game_start){ global_time++; player.proc(enemyPool[0]); autoPlayer.proc(enemyPool[0]); // 自機と敵機が近づきすぎていたら離す if(AUTOPLAY){ for (int i=0; i<enemyPool.size(); i++) { enemyPool[i].proc(autoPlayer); } for (int i=0; i<enemyPool.size(); i++) { if(autoPlayer.distance(enemyPool[i]) < 2.0){ autoPlayer.move(-1.0); enemyPool[i].move(-1.0); } } }else{ for (int i=0; i<enemyPool.size(); i++) { enemyPool[i].proc(player); } for (int i=0; i<enemyPool.size(); i++) { if(player.distance(enemyPool[i]) < 2.0){ player.move(-1.0); enemyPool[i].move(-1.0); } } } // 敵機同士でも近づきすぎてたら離す for (int i=0; i<enemyPool.size(); i++) { for (int k=i+1; k<enemyPool.size(); k++) { if (enemyPool[i].distance(enemyPool[k]) < 2.0) { enemyPool[i].move(0.5); enemyPool[k].move(-0.5); } } } // 弾丸の当たり判定 if(AUTOPLAY){ for (int i=0; i<enemyPool.size(); i++) { if (autoPlayer.check(enemyPool[i])){ // 敵の消滅処理 enemyPool[i].attackable = false; } } }else{ for (int i=0; i<enemyPool.size(); i++) { if (player.check(enemyPool[i])){ enemyPool[i].attackable = false; } } } for (int i=0; i<enemyPool.size(); i++) { if(AUTOPLAY){ enemyPool[i].check(autoPlayer); }else{ enemyPool[i].check(player); } } // 敵同士の当たり判定も考える(総当り) for (int i=0; i<enemyPool.size(); i++) { if(!enemyPool[i].isExist) continue; for (int k=0; k<enemyPool.size(); k++) { if (i==k) { continue; } if (enemyPool[i].check(enemyPool[k])) { // 敵の消滅処理 enemyPool[k].attackable = false; //enemyPool[k].isExist = false; } } } // 最後にまとめて消す vector<EnemyTank>::iterator it; for(it=enemyPool.begin(); it!=enemyPool.end();){ if((*it).isExist == false){ it = enemyPool.erase(it); }else{ it++; } } if (enemyPool.size() == 0) { nextStageInit(); return; } if(AUTOPLAY){ if(!autoPlayer.isExist && autoPlayer.expTimer==10){ nextStageInit(); return; } } } return; } void glInit(){ glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH); if(FULLSCREEN_MODE){ glutGameModeString("1024x768:32@60"); glutEnterGameMode(); }else{ glutInitWindowPosition(100, 100); glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT); glutCreateWindow("Tank Action! (OpenGL/GLUT)"); } glutReshapeFunc(reshape); glutDisplayFunc(display); // キー操作 glutKeyboardFunc(keyDown); glutKeyboardUpFunc(keyUp); glutSpecialFunc(specialKeyDown); glutSpecialUpFunc(specialKeyUp); glClearColor(0, 0, 0, 0); // 光源 GLfloat light1Pos[] = {-16, 10, -16, 1}; GLfloat light2Pos[] = {16, 10, -16, 1}; GLfloat light3Pos[] = {16, 10, 16, 1}; GLfloat light4Pos[] = {-16, 10, 16, 1}; GLfloat lightCol[] = {1.0, 1.0, 1.0, 1}; glLightfv(GL_LIGHT0, GL_POSITION, light1Pos); glLightfv(GL_LIGHT0, GL_DIFFUSE, lightCol); glLightf(GL_LIGHT0, GL_SPECULAR, 1.0); //glLightf(GL_LIGHT0, GL_DIFFUSE, 0.7); glLightf(GL_LIGHT0, GL_AMBIENT, 0.2); glLightfv(GL_LIGHT1, GL_POSITION, light2Pos); glLightfv(GL_LIGHT1, GL_DIFFUSE, lightCol); glLightf(GL_LIGHT1, GL_SPECULAR, 1.0); glLightf(GL_LIGHT1, GL_AMBIENT, 0.2); glLightfv(GL_LIGHT2, GL_POSITION, light3Pos); glLightfv(GL_LIGHT2, GL_DIFFUSE, lightCol); glLightf(GL_LIGHT2, GL_SPECULAR, 1.0); glLightf(GL_LIGHT2, GL_AMBIENT, 0.2); glLightfv(GL_LIGHT3, GL_POSITION, light4Pos); glLightfv(GL_LIGHT3, GL_DIFFUSE, lightCol); glLightf(GL_LIGHT3, GL_SPECULAR, 1.0); glLightf(GL_LIGHT3, GL_AMBIENT, 0.2); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_LIGHT1); glEnable(GL_LIGHT2); glEnable(GL_LIGHT3); glEnable(GL_DEPTH_TEST); } // 各種初期化とメインループへの移行 int main(int argc, char *argv[]){ glutInit(&argc, argv); glInit(); #if HEADTRACKING_MODE cvInit(); #endif gameInit(); nextStageInit(); glutTimerFunc(50, timer, 0); glutMainLoop(); return 0; }
GLColorクラス
#ifndef GLColor_h #define GLColor_h // GL_DIFFUSE用の色 // C++のpublicでstaticなメンバ変数の初期化は気持ち悪い.javaみたいに書きたい. class GLColor { public: static GLfloat BLACK[]; static GLfloat WHITE[]; static GLfloat GRAY[]; static GLfloat RED[]; static GLfloat DARKRED[]; static GLfloat GREEN[]; static GLfloat GREEN2[]; static GLfloat DARKGREEN[]; static GLfloat BLUE[]; static GLfloat SKYBLUE[]; static GLfloat YERROW[]; static GLfloat SKYYERROW[]; static GLfloat PURPLE[]; }; GLfloat GLColor::BLACK[] = {0.1, 0.1, 0.1, 0}; GLfloat GLColor::WHITE[] = {0.8, 0.8, 0.8, 0}; GLfloat GLColor::GRAY[] = {0.4, 0.4, 0.4, 0}; GLfloat GLColor::RED[] = {0.5, 0, 0, 0}; GLfloat GLColor::DARKRED[] = {0.2, 0, 0, 0}; GLfloat GLColor::GREEN[] = {0, 0.5, 0, 0}; GLfloat GLColor::GREEN2[] = {0.01, 0.4, 0.01, 0}; GLfloat GLColor::DARKGREEN[] = {0, 0.2, 0, 0}; GLfloat GLColor::BLUE[] = {0, 0, 0.5, 0}; GLfloat GLColor::SKYBLUE[] = {124.0/256.0, 243.0/256.0, 243.0/256.0, 0}; GLfloat GLColor::YERROW[] = {0.5, 0.5, 0, 0}; GLfloat GLColor::SKYYERROW[] = {0.8, 0.8, 0, 0}; GLfloat GLColor::PURPLE[] = {146.0/256.0, 36.0/256.0, 1.0, 0}; #endif
GLObjectクラス
#ifndef GLObject_h #define GLObject_h class GLObject { public: float x, y, z; // 座標 float vec_x, vec_z; // 移動量 float rotate; // 向き bool isExist; // 1なら存在する、0なら画面上にいない GLObject() { reset(); } void reset(){ x = y = z = vec_x = vec_z = rotate = 0; isExist = false; } // rotate に従って delta だけ前進 void move(float delta){ float fR = 360.0 - (rotate + 90.0); x += cos(fR * PI / 180.0) * delta; z += sin(fR * PI / 180.0) * delta; } void proc(){ } void display(){ } void setStatus(GLObject obj){ x = obj.x; y = obj.y; z = obj.z; rotate = obj.rotate; isExist = obj.isExist; } float distance(GLObject p){ return sqrt((p.x-x)*(p.x-x) + (p.z-z)*(p.z-z)); } float direction(GLObject p){ return atan2(x-p.x, z-p.z) * 180.0/PI; } }; #endif
Tankクラス extends GLObject
#include "GLObject.h" #include "Ball.h" #include "GLColor.h" #ifndef Tank_h #define Tank_h class Tank : public GLObject { protected: // 画面外から出ないようにする void validatePosition(){ if(x > 15.5) x = 15; else if(x < -15.5) x = -15; if(z > 15.5) z = 15; else if(z < -15.5) z = -15; } bool isInField(){ if(x > 15) return false; else if(x < -15) return false; if(z > 15) return false; else if(z < -15) return false; return true; } // 玉を発射するための設定 void setShoot(float delta){ lastshoot = global_time; ball[tid].x = x; ball[tid].z = z; ball[tid].rotate = rotate; ball[tid].type = type; ball[tid].expTimer = 0; ball[tid].y = 0.9; float fR = 360.0 - (ball[tid].rotate + 90.0) + delta; ball[tid].rotate = rotate-delta; ball[tid].vec_x = 0.5*cos(fR * PI / 180.0); ball[tid].vec_z = 0.5*sin(fR * PI / 180.0); ball[tid].isExist = true; if(tid < MAX_BALL-1) tid++; else tid = 0; } // レベルに応じて発射角度を変更 void multiShoot(){ if(!isExist) return; setShoot(0); if(enemy_level >= 3){ setShoot(180); } if(enemy_level >= 6){ setShoot(60); setShoot(-60); } if(enemy_level >= 9){ setShoot(90); setShoot(-90); } } void displayTank(){ // type に依存 glPushMatrix(); if(type == PLAYER){ glTranslatef(x, y+0.5, z ); glRotatef(rotate+90, 0.0, 1.0, 0.0); }else{ glTranslatef(x, y+0.5, z ); glRotatef(rotate+90, 0.0, 1.0, 0.0); } glMaterialf(GL_FRONT, GL_AMBIENT, 0.1); glMaterialf(GL_FRONT, GL_SPECULAR, 0.1); glMaterialf(GL_FRONT, GL_SHININESS, 0); glMaterialfv(GL_FRONT, GL_DIFFUSE, type == PLAYER ? GLColor::RED : GLColor::BLUE); glPushMatrix(); glTranslatef(0, 0.5, 0); glutSolidCube(1); glPopMatrix(); // タイヤシャフト右 if (life > maxLife*2/3) { glPushMatrix(); glTranslatef(0, 0.5, 0.5); glutSolidCone(0.5, 1.5, 10, 10); glPopMatrix(); } // タイヤシャフト左 if (life > maxLife*1/3) { glPushMatrix(); glTranslatef(0, 0.5, -0.5); glRotatef(180, 0, 1, 0); glutSolidCone(0.5, 1.5, 10, 10); glPopMatrix(); } // フロントの針 glMaterialf(GL_FRONT, GL_SHININESS, 60); glMaterialf(GL_FRONT, GL_SPECULAR, 1.0); glMaterialfv(GL_FRONT, GL_DIFFUSE, GLColor::WHITE); glPushMatrix(); glTranslatef(0.5, 0.5, 0); glRotatef(90, 0, 1, 0); glutSolidCone(0.5, 0.5+2*life/maxLife, 10, 10); glPopMatrix(); // タイヤ glMaterialf(GL_FRONT, GL_SHININESS, 0); glMaterialf(GL_FRONT, GL_SPECULAR, 0.1); glMaterialfv(GL_FRONT, GL_DIFFUSE, GLColor::BLACK); if (life > maxLife*2/3) { glPushMatrix(); glTranslatef(0, 0.5, 1); glRotatef(type == PLAYER ? whrightrot : whrightrot, 0, 0, 1); glutSolidTorus(0.3, 1.0, 5, 5); glPopMatrix(); } if (life > maxLife*1/3) { glPushMatrix(); glTranslatef(0, 0.5, -1); glRotatef(type == PLAYER ? whrightrot : whrightrot, 0, 0, 1); glutSolidTorus(0.3, 1.0, 5, 5); glPopMatrix(); } glPopMatrix(); } void displayExp(float x, float y, float z){ glPushMatrix(); glTranslatef(x, y, z); //glRotatef(rotate, 0.0, 1.0, 0.0); glMaterialfv(GL_FRONT, GL_DIFFUSE, GLColor::RED); glRotatef(expTimer*72, 0, 1, 0); //glutWireSphere(0.5+0.05*expTimer*expTimer, 10, 10); glutSolidSphere(0.5+0.05*expTimer*expTimer, 4, 5); glPopMatrix(); } public: Ball ball[MAX_BALL]; // 弾丸 int tid; // 玉id float whrightrot, whleftrot; // タイヤの回転角度 int maxLife; // 開始時の寿命 int life; // 寿命 bool attackable; // true なら攻撃可能、falseなら攻撃不可 unsigned long long lastshoot; // 最後に玉を撃った時刻 int type; int expTimer; // 爆発アニメーション用のタイマ // コンストラクタではあえてsuperのコンストラクタを呼ばない(resetの2重呼び出しになってしまう) Tank(){ reset(); } void reset(){ GLObject::reset(); whrightrot = whleftrot = 0; isExist = true; lastshoot = global_time; maxLife = life = pow((enemy_level+1), 2.0); attackable = true; expTimer = -1; tid = 0; for (int i=0; i<MAX_BALL; i++) { ball[i].reset(); } } void setStatus(Tank &tank){ GLObject::setStatus(tank); /* x = tank.x; y = tank.y; z = tank.z; rotate = tank.rotate; */ life = tank.life; maxLife = tank.maxLife; expTimer = tank.expTimer; lastshoot = tank.lastshoot; tid = tank.tid; for (int i=0; i<tid; i++) { ball[i].setStatus(tank.ball[i]); tank.ball[i].isExist = false; } } void move(float delta){ GLObject::move(delta); if(delta > 0){ whrightrot -= 10; whleftrot -= 10; }else{ whrightrot += 10; whleftrot += 10; } } void proc(Tank &tank){ GLObject::proc(); if(!isExist) return; // 弾丸の処理 for (int i=0; i<MAX_BALL; i++) { if(ball[i].isExist){ // 範囲外なら if(ball[i].x > 15.5 || ball[i].x < -15.5 || ball[i].z > 15.5 || ball[i].z < -15.5){ ball[i].isExist = false; ball[i].expTimer = 0; ball[i].last_type = 0; }else{ ball[i].x += ball[i].vec_x; ball[i].z += ball[i].vec_z; } } } } void display(){ GLObject::display(); if(!isExist) return; if(expTimer == -1){ displayTank(); // 玉 for(int i=0; i<MAX_BALL; i++){ ball[i].display(); } }else if(expTimer < 5){ displayExp(x+1, y, z+1); displayExp(x-1, y, z-1); //displayExp(x+1, y, z-0.5); expTimer++; }else if(expTimer < 10){ displayExp(x-1, y, z+1); displayExp(x+1, y, z-1); expTimer++; }else if(expTimer == 10){ isExist = false; } } // 相手のタンクが自分のタンクの弾丸に触れているか // 触れていたら適切に処理 // このcheckがきっかけになって相手のタンクが破壊された場合trueを返す bool check(Tank &t){ // なかったり,爆発アニメーション中はチェックしない if (!isExist || !t.isExist || t.expTimer != -1) { return false; } for(int i=0; i<MAX_BALL; i++){ if(ball[i].isExist){ // 玉と相手の当たり判定 // もし玉と相手の距離が指定値以下だったら if (ball[i].distance(t) <= 1.0 + life/maxLife) { t.life--; ball[i].isExist = false; ball[i].expTimer = 0; ball[i].last_type = 1; if (t.life <= 0) { //t.isExist = false; t.expTimer = 0; return true; } } } } return false; } void shoot(){ if (AUTOPLAY) { if(ball[tid].isExist == 0 && attackable == 1 && global_time - lastshoot > 1.0){ multiShoot(); } }else{ if(ball[tid].isExist == 0 && attackable == 1 && global_time - lastshoot > 10.0-enemy_level){ multiShoot(); } } } }; #endif
PlayerTankクラス extends Tank
ユーザーの手によって手動で動かすTank
#include "Tank.h" #ifndef PlayerTank_h #define PlayerTank_h class PlayerTank : public Tank { public: PlayerTank(){ reset(); } void reset(int initType){ reset(); type = initType; } void reset(){ Tank::reset(); z = 13; type = PLAYER; } // tankは使わないけど,インターフェースを統一して拡張性を高める void proc(Tank &tank){ Tank::proc(tank); // もし存在フラグが消えていたら何もせずに戻る if(isExist == false) return; if(key_up == 1){ // 前進 move(0.5); // 0.5くらいがちょうどいい } if(key_down == 1){ // 後退 move(-0.5); } if(key_left == 1){ // 右旋回 rotate += 10; whrightrot -= 10; whleftrot += 10; } if(key_right == 1){ // 左旋回 rotate -= 10; whrightrot += 10; whleftrot -= 10; } /* // 画面外から出ないようにする if(x > 15.5) x -= 0.5; else if(x < -15.5) x += 0.5; if(z > 15.5) z -= 0.5; else if(z < -15.5) z += 0.5; */ validatePosition(); // 射撃 if(key_space == 1){ shoot(); } } }; #endif
EnemyTankクラス extends Tank
相手の動きに応じて自律して動作するTank
#include "Tank.h" #ifndef EnemyTank_h #define EnemyTank_h class EnemyTank : public Tank { private: void procForceAtack(Tank &tank){ rotate = direction(tank) + rand()%10-5; move(0.5); } void procNormalAtack(Tank &tank){ // ピンチになったら逃げ惑う // if (life < maxLife/5) { // isDanger = true; // last_safe_time = global_time; // } if (isDanger == false) { // 自機に玉があたる方向を向く float diff = direction(tank); if(enemy_level >= 9){ if(abs(diff - rotate) > abs(diff+60 - rotate)) diff += 60; else if(abs(diff - rotate) > abs(diff-60 - rotate)) diff -= 60; else if(abs(diff - rotate) > abs(diff+90 - rotate)) diff += 90; else if(abs(diff - rotate) > abs(diff-90 - rotate)) diff -= 90; else if(abs(diff - rotate) > abs(diff+180 - rotate)) diff += 180; }else if(enemy_level >= 6){ if(abs(diff - rotate) > abs(diff+60 - rotate)) diff += 60; else if(abs(diff - rotate) > abs(diff-60 - rotate)) diff -= 60; else if(abs(diff - rotate) > abs(diff+180 - rotate)) diff += 180; }else if(enemy_level >= 3){ if(abs(diff - rotate) > abs(diff+180 - rotate)) diff += 180; } // 少しだけ散乱させて避けにくくする rotate = diff + (rand()%30 - 15); } if(isDanger == false){ move(0.3); for(int i=0; i<tank.tid; i++){ // 近くに来ている自機の玉 if(tank.ball[i].isExist == true){ float d = distance(tank.ball[i]); if (d < 2.5) { isDanger = true; last_safe_time = global_time; if(enemy_level < 6){ // あたらない方向にちょっとだけ瞬間移動する rotate = direction(ball[i]); rotate += rand()%100 < 50 ? 90 : -90; move(0.1 * enemy_level); } break; }else if(d < 4.0){ move(-0.6); break; } } } }else{ // 強制完全回避モード if(global_time - last_safe_time < 10+2*enemy_level){ rotate += rand()%6-3; float max = 0.5 + enemy_level/40; if (life < maxLife/5) { max *= 3; } for (float step=max; step>=-max; step-=0.1) { bool moved = true; move(step); if(!isInField()){ move(-step); continue; } for(int i=0; i<tank.tid; i++){ // 近くに来ている自機の玉 if(tank.ball[i].isExist == true){ float d = distance(tank.ball[i]); if (d < 2.0){ move(-step); moved = false; break; } } } if (moved) { //validatePosition(); break; } } }else{ isDanger = false; } } } public: bool isDanger; // 危険なときtrue int last_safe_time; // 最後に安全モードだった時刻 EnemyTank(){ reset(); } void reset(int initType){ reset(); type = initType; } void reset(){ Tank::reset(); //z = -13; z = -rand()%16; x = rand()%32-16; type = ENEMY; last_safe_time = global_time; isDanger = false; } // EnemyTankは相手の行動を元に自らの行動を決定する void proc(Tank &tank){ Tank::proc(tank); if(!isExist || !tank.isExist) return; // ライフに圧倒的な差があったら特攻する if(life > tank.life*2 && life - tank.life > 10){ procForceAtack(tank); }else{ procNormalAtack(tank); } // 画面外から出ないようにする if(x > 15.5){ x -= 0.5; //rotate = rand()%360-720; rotate = direction(tank); rotate += rand()%100 < 50 ? 90 : -90; }else if(x < -15.5){ x += 0.5; //rotate = rand()%360-720; rotate = direction(tank); rotate += rand()%100 < 50 ? 90 : -90; } if(z > 15.5){ z -= 0.5; //rotate = rand()%360-720; rotate = direction(tank); rotate += rand()%100 < 50 ? 90 : -90; }else if(z < -15.5){ z += 0.5; //rotate = rand()%360-720; rotate = direction(tank); rotate += rand()%100 < 50 ? 90 : -90; } // 射撃 shoot(); } }; #endif
Ballクラス extends GLObject
Tankが出す弾丸
#include "GLObject.h" #include "GLColor.h" #ifndef Ball_h #define Ball_h class Ball : public GLObject { public: int expTimer; // 爆発アニメーション用のタイマ int type; // PLAYER or ENEMY int last_type; // 爆発原因 0:壁 1:enemy or player Ball(){ reset(); } void reset(){ GLObject::reset(); type = 0; expTimer = -1; last_type = 0; } void setStatus(Ball &ball){ GLObject::setStatus(ball); vec_x = ball.vec_x; vec_z = ball.vec_z; } void display(){ GLObject::display(); if(isExist){ displayBall(); }else{ if(expTimer >= 0 && expTimer < 10){ displayExp(); expTimer++; }else if(expTimer == 10){ reset(); } } } // 弾丸みたいな玉を表示する void displayBall(){ glPushMatrix(); glTranslatef(x, y, z); glRotatef(rotate, 0.0, 1.0, 0.0); glMaterialf(GL_FRONT, GL_AMBIENT, 0.1); glMaterialf(GL_FRONT, GL_SPECULAR, 1.0); glMaterialf(GL_FRONT, GL_SHININESS, 60); glMaterialfv(GL_FRONT, GL_DIFFUSE, type==PLAYER ? GLColor::YERROW : GLColor::PURPLE); // 自機 or 敵機で玉の色を変える glPushMatrix(); glTranslatef(0, 0.1, 0); glutSolidSphere(0.3, 5, 5); glPopMatrix(); glPushMatrix(); glTranslatef(0, 0.1, 0); glRotatef(180, 0, 1, 0); glutSolidCone(0.3, 2, 5, 5); glPopMatrix(); glPopMatrix(); } // 爆発アニメーションを表示する void displayExp(){ glPushMatrix(); glTranslatef(x, y, z); glRotatef(rotate, 0.0, 1.0, 0.0); glMaterialfv(GL_FRONT, GL_DIFFUSE, last_type ? GLColor::RED : GLColor::GRAY); glRotatef(expTimer*72, 0, 1, 0); // 壁なら灰色,ダメージなら赤色 glutWireSphere(last_type ? 1+0.015*expTimer*expTimer : 0.5+0.01*expTimer*expTimer, 10, 10); //glutSolidSphereはタンクが爆発したときに使うようにする glPopMatrix(); } }; #endif
Fieldクラス extends GLObject
ステージや背景など
#include "GLObject.h" #ifndef Field_h #define Field_h // 床や空,柵 class Field : public GLObject { private: float fieldSize; // フィールドの大きさ float split; // 分割数 float tileSize; // 分割後のタイルの大きさ float tileHeight; // 床のデコボコの高さ public: Field(){ reset(); } void reset(){ GLObject::reset(); fieldSize = 32; split = 16; tileSize = fieldSize/split; tileHeight = 1.0; } void display(){ GLObject::display(); glMaterialf(GL_FRONT, GL_AMBIENT, 0.1); glMaterialf(GL_FRONT, GL_SPECULAR, 0.1); glMaterialf(GL_FRONT, GL_SHININESS, 0.0); // 床 for (int z=0; z<split; z++) { for (int x=0; x<split; x++) { float baseX = -fieldSize/2 + x*tileSize; float baseZ = -fieldSize/2 + z*tileSize; if ((x+z)%2 == 0) { glMaterialfv(GL_FRONT, GL_DIFFUSE, GLColor::GREEN); }else{ glMaterialfv(GL_FRONT, GL_DIFFUSE, GLColor::GREEN2); } glBegin(GL_QUADS); glNormal3d(0, 1, 0); glVertex3d(baseX , (z%2==1)&&(x%2==1)?tileHeight:0, baseZ); glVertex3d(baseX+tileSize, (z%2==1)&&(x%2==0)?tileHeight:0, baseZ); glVertex3d(baseX+tileSize, (z%2==0)&&(x%2==0)?tileHeight:0, baseZ+tileSize); glVertex3d(baseX, (z%2==0)&&(x%2==1)?tileHeight:0, baseZ+tileSize); glEnd(); } } // 壁奥 // enemy_levelが上がるにつれて色を灰色から赤色に変えていく GLfloat gradred[] = {0.4-enemy_level*0.05 + enemy_level*0.1, 0.4-enemy_level*0.05, 0.4-enemy_level*0.05, 0}; glMaterialfv(GL_FRONT, GL_DIFFUSE, gradred); for(int i=-16; i<=16; i++){ glPushMatrix(); glTranslatef(i, 0.5, -16); glRotatef(-90, 1, 0, 0); glutSolidCone(0.5, 2, 5, 5); glPopMatrix(); } // 壁手前 for(int i=-16; i<=16; i++){ glPushMatrix(); glTranslatef(i, 0.5, 16); glRotatef(-90, 1, 0, 0); glutSolidCone(0.5, 2, 5, 5); glPopMatrix(); } // 壁右 for(int i=-16; i<=16; i++){ glPushMatrix(); glTranslatef(16, 0.5, i); glRotatef(-90, 1, 0, 0); glutSolidCone(0.5, 2, 5, 5); glPopMatrix(); } // 壁左 for(int i=-16; i<=16; i++){ glPushMatrix(); glTranslatef(-16, 0.5, i); glRotatef(-90, 1, 0, 0); glutSolidCone(0.5, 2, 5, 5); glPopMatrix(); } // 下の空 glMaterialfv(GL_FRONT, GL_DIFFUSE, GLColor::SKYBLUE); glBegin(GL_QUADS); // 床 glNormal3d(0, 1, 0); glVertex3d(-32.0, -1.0, -32.0); glVertex3d(32.0, -1.0, -32.0); glVertex3d(32.0, -1.0, 32.0); glVertex3d(-32.0, -1.0, 32.0); glEnd(); } }; #endif
Parameterクラス extends GLObject
文字など表示するためのクラス.OpenGLでの文字列表示は環境依存が激しいため,Soji Yamakawaさん(The Department of Mechanical Engineering Carnegie Mellon University)が作成したDrawing a String of Characters using OpenGL.を使っています.
#include <string> #include "GLObject.h" #include "GLColor.h" #include "uglyfont.h" // http://ysflight.in.coocan.jp/programming/uglyfont/uglyfontj.html #ifndef Parameter_h #define Parameter_h // 画面左上に文字を表示する class Parameter : public GLObject { private: // 環境依存が激しすぎるので廃止 // x, yは画面の右下が原点なことに注意 /*void displayBitmapCharacters(int x, int y, char *string, void *font){ int len, i; glRasterPos2f(x, y); len = (int)strlen(string); for(i=0; i<len; i++){ glutBitmapCharacter(font, string[i]); } } void displayText(int x, int y, string str){ char buff[256]; strcpy(buff, str.c_str()); displayText(x, y, buff); } void displayText(int x, int y, char *str){ glPushAttrib(GL_ENABLE_BIT); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(0, 100, 0, 100); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glColor3f(0.0, 0.0, 0.0); glCallList(list); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glPopAttrib(); glMatrixMode(GL_MODELVIEW); list = glGenLists(1); glNewList(list, GL_COMPILE); displayBitmapCharacters(x, y, str, GLUT_BITMAP_TIMES_ROMAN_24); glEndList(); } */ public: void display(int playerLife, int enemyLife){ GLObject::display(); glDisable(GL_LIGHTING); // glColor を有効にする glColor3f(0, 0, 0); char buff[256]; sprintf(buff, "playerLife:%d enemyLife:%d level:%d enemySize:%d", playerLife, enemyLife, enemy_level, enemySize); glPushMatrix(); glTranslatef(0,5,-15); glScalef(0.8, 1.2, 0.8); glRotatef(-60, 1, 0, 0); YsDrawUglyFont(buff, 1); glPopMatrix(); if(playerLife == 0 && !AUTOPLAY){ displayGameOver(); } /*if(enemy_level >= 10){ sprintf(t, "You won! or continue...?"); displayText(60, 95, t); }*/ glEnable(GL_LIGHTING); } void displayGameOver(){ glPushMatrix(); glTranslatef(0,1.5,0); glScalef(1.8,1.8,1.8); glRotatef(-60, 1, 0, 0); glColor3f(1, 0, 0); YsDrawUglyFont("G A M E O V E R", 1); glTranslatef(0.1, -0.2, 0.1); glColor3f(0, 0, 0); YsDrawUglyFont("G A M E O V E R", 1); glPopMatrix(); glPushMatrix(); glTranslatef(0,1.5,2.5); glScalef(1.5,1.5,1.5); glRotatef(-60, 1, 0, 0); glColor3f(1, 0, 0); YsDrawUglyFont("Press r to reset", 1); glTranslatef(0.1, -0.2, 0.1); glColor3f(0, 0, 0); YsDrawUglyFont("Press r to reset", 1); glPopMatrix(); } }; #endif
ヘッドトラッキングのサンプル
OpenCVを使ったヘッドトラッキングのサンプルプログラムです.TankActionのベースになっています. コンパイルする際は上記のGLObject.hとGLColor.hが必要です. カーソルキーの上下で洞穴を前後に移動できて,顔を動かすと洞穴を見回せます.
main.cpp
#include <iostream> #include <cstdlib> #include <cmath> #include <ctime> #include <string> #include <vector> #include <queue> using namespace std; // #include <GL/glut.h> // 環境によって変える //#include "./glut.h" #include <GLUT/glut.h> // for Mac #pragma comment(lib, "./glut32.lib") #define PI 3.141592 #define FULLSCREEN_MODE 0 // 0ならウィンドウ,1ならフルスクリーンでマウス非表示 #define WINDOW_WIDTH 640 #define WINDOW_HEIGHT 480 int key_up = 0, key_down = 0, key_left = 0, key_right = 0, key_space = 0; // キー状態を記録 unsigned long long global_time = 0; float faceX = 0, faceY = 0, faceRadius = 1.0; float myFaceX = WINDOW_WIDTH/2, myFaceY = WINDOW_HEIGHT/2 + 100; float myX = 0, myY = 0, myZ = 35; #include "GLColor.h" #include "LongHall.h" LongHall hall; #include <opencv2/objdetect/objdetect.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #define IMG_SCALE 2 // 1だと重すぎる,4だとちょっと離れたら認識しなくなる using namespace cv; queue<pair<float, pair<float, float> > > peekQue; // peek用キュー <radius, <x, y>> String cascadeName = "/Users/usi3/Desktop/OpenCV-2.2.0/data/haarcascades/haarcascade_frontalface_alt.xml"; CvCapture* capture = 0; Mat frame, frameCopy, image; CascadeClassifier cascade; // キー操作 void keyDown(unsigned char key, int x, int y){ switch(key){ case ' ': key_space = 1; break; case '\33': if(FULLSCREEN_MODE) glutLeaveGameMode(); exit(0); break; case 'e': myFaceX = faceX; myFaceY = faceY; break; default: break; } } void keyUp(unsigned char key, int x, int y){ switch(key){ case ' ': key_space = 0; break; default: break; } } void specialKeyDown(int key, int x, int y){ switch(key){ case GLUT_KEY_UP: key_up = 1; break; case GLUT_KEY_DOWN: key_down = 1; break; case GLUT_KEY_LEFT: key_left = 1; break; case GLUT_KEY_RIGHT: key_right = 1; break; default: break; } } void specialKeyUp(int key, int x, int y){ switch(key){ case GLUT_KEY_UP: key_up = 0; break; case GLUT_KEY_DOWN: key_down = 0; break; case GLUT_KEY_LEFT: key_left = 0; break; case GLUT_KEY_RIGHT: key_right = 0; break; default: break; } } // 表示制御 void display(void){ glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (!peekQue.empty()) { pair<float, pair<float, float> > faceInfo = peekQue.back(); peekQue.pop(); faceX += (faceInfo.second.first/12 - faceX)*0.1; faceY += (faceInfo.second.second/6 - faceY)*0.1; faceRadius += ((faceInfo.first-30) - faceRadius)*0.1; //cout << "faceY = " << faceY << endl; cout << "faceRadius = " << faceRadius << endl; } // 視点変更 glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(32.0, (double)WINDOW_WIDTH / (double)WINDOW_HEIGHT, 1.0, 300.0); glTranslated(0.0, 0.0, 0.0); //gluLookAt(0.0, 0.0, myZ-faceRadius, faceX, faceY, myZ-35-faceRadius, 0.0, 1.0, 0.0); gluLookAt(0.0, 0.0, myZ, faceX, faceY, myZ-35, 0.0, 1.0, 0.0); // オブジェクト描画 glMatrixMode(GL_MODELVIEW); glLoadIdentity(); //glRotatef(-faceX, 0, 0, 1); //glRotatef(-faceY, 1, 0, 0); //glScaled(faceRadius, faceRadius, faceRadius); // フィールド hall.display(); // ダブルバッファリング glutSwapBuffers(); } // 画面 and 視点制御 void reshape(GLsizei w, GLsizei h){ // 画面の大きさを変更する cout << "reshape w = " << w << "h = " << h << endl; glutReshapeWindow(WINDOW_WIDTH, WINDOW_HEIGHT); glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(32.0, (double)w / (double)h, 1.0, 300.0); glTranslated(0.0, 0.0, 0.0); gluLookAt(0.0, 0.0, 35.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); } void detectAndDraw(Mat& img, CascadeClassifier& cascade){ vector<Rect> faces; // CV_8UC1: 1チャネル8ビットunsigned char Mat gray, smallImg(cvRound(img.rows/IMG_SCALE), cvRound(img.cols/IMG_SCALE), CV_8UC1); // 色空間をカラーからグレースケールへ変換 cvtColor(img, gray, CV_BGR2GRAY); // IMG_SCALEに応じて小さくする resize(gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR); equalizeHist(smallImg, smallImg); double t = (double)cvGetTickCount(); cascade.detectMultiScale( smallImg, faces, 1.1, 2, 0 //|CV_HAAR_FIND_BIGGEST_OBJECT //|CV_HAAR_DO_ROUGH_SEARCH |CV_HAAR_SCALE_IMAGE , Size(30, 30) ); t = (double)cvGetTickCount() - t; //printf("detection time = %g ms\n", t/((double)cvGetTickFrequency()*1000.)); // 1つの顔にしか対応しないことにする vector<Rect>::const_iterator r = faces.begin(); // 顔が1つ以上検出されているなら最初の顔のみについて処理 if (r != faces.end()) { Point center; center.x = cvRound((r->x + r->width*0.5)*IMG_SCALE); center.y = cvRound((r->y + r->height*0.5)*IMG_SCALE); //cout << center.x << " " << center.y << endl; // ディスプレイを正面から見たとき顔は下の方に映るからyを半分引いてない. float radius = cvRound((r->width + r->height)*0.25*IMG_SCALE); circle( img, center, radius, CV_RGB(0,0,255), 3, 8, 0 ); // radius 34 - 190 くらい peekQue.push(make_pair(radius, make_pair(center.x-myFaceX, center.y-myFaceY))); } imshow("result", img); } void cvTimer(){ // for debug //if (rand()%100 < 10) { //int x = cos(global_time/5)*320; //int y = sin(global_time/5)*320; //peekQue.push(make_pair(x, y)); //} IplImage* iplImg = cvQueryFrame(capture); frame = iplImg; if(frame.empty()){ return; } if(iplImg->origin == IPL_ORIGIN_TL){ frame.copyTo(frameCopy); }else{ flip(frame, frameCopy, 0); } detectAndDraw(frameCopy, cascade); } // メインループ void timer(int value){ glutPostRedisplay(); glutTimerFunc(50, timer, 0); cvTimer(); global_time++; if (key_up == 1) { myZ -= 1; } if (key_down == 1) { myZ += 1; } } void glInit(){ glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH); if(FULLSCREEN_MODE){ glutGameModeString("1024x768:32@60"); glutEnterGameMode(); }else{ glutInitWindowPosition(100, 100); glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT); glutCreateWindow("HeadTrackingSample (OpenGL/GLUT)"); } glutReshapeFunc(reshape); glutDisplayFunc(display); // キー操作 glutKeyboardFunc(keyDown); glutKeyboardUpFunc(keyUp); glutSpecialFunc(specialKeyDown); glutSpecialUpFunc(specialKeyUp); glClearColor(0, 0, 0, 0); // 光源 GLfloat light1Pos[] = {-5, -5, 35, 1}; GLfloat light2Pos[] = {-5, 5, 35, 1}; GLfloat light3Pos[] = {5, 5, 35, 1}; GLfloat light4Pos[] = {5, -5, 35, 1}; GLfloat lightCol[] = {1.0, 1.0, 1.0, 1}; glLightfv(GL_LIGHT0, GL_POSITION, light1Pos); glLightfv(GL_LIGHT0, GL_DIFFUSE, lightCol); glLightf(GL_LIGHT0, GL_SPECULAR, 1.0); glLightf(GL_LIGHT0, GL_AMBIENT, 0.2); glLightfv(GL_LIGHT1, GL_POSITION, light2Pos); glLightfv(GL_LIGHT1, GL_DIFFUSE, lightCol); glLightf(GL_LIGHT1, GL_SPECULAR, 1.0); glLightf(GL_LIGHT1, GL_AMBIENT, 0.2); glLightfv(GL_LIGHT2, GL_POSITION, light3Pos); glLightfv(GL_LIGHT2, GL_DIFFUSE, lightCol); glLightf(GL_LIGHT2, GL_SPECULAR, 1.0); glLightf(GL_LIGHT2, GL_AMBIENT, 0.2); glLightfv(GL_LIGHT3, GL_POSITION, light4Pos); glLightfv(GL_LIGHT3, GL_DIFFUSE, lightCol); glLightf(GL_LIGHT3, GL_SPECULAR, 1.0); glLightf(GL_LIGHT3, GL_AMBIENT, 0.2); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_LIGHT1); glEnable(GL_LIGHT2); glEnable(GL_LIGHT3); glEnable(GL_DEPTH_TEST); } void cvInit(){ if(!cascade.load(cascadeName)){ cerr << "ERROR: Could not load classifier cascade" << endl; return; } capture = cvCaptureFromCAM(0); if(!capture){ cerr << "Capture from CAM didn't work" << endl; return; } cvNamedWindow("result", 1); } // 各種初期化とメインループへの移行 int main(int argc, char *argv[]){ srand(time(NULL)); glutInit(&argc, argv); glInit(); cvInit(); glutTimerFunc(50, timer, 0); glutMainLoop(); return 0; }
LongHallクラス
無限に続くかのような洞穴を表現します
#include "GLObject.h" #ifndef LongHall_h #define LongHall_h class LongHall : public GLObject { private: float squareSize; // フィールドの大きさ float split; // 分割数 float tileSize; // 分割後のタイルの大きさ float tileHeight; // 床のデコボコの高さ public: LongHall(){ reset(); } void reset(){ GLObject::reset(); squareSize = 32; split = 16; tileSize = squareSize/split; tileHeight = 1.0; } // TankActionからの流用 void displayFloor(){ for (int z=0; z<split; z++) { for (int x=0; x<split; x++) { float baseX = -squareSize/2 + x*tileSize; float baseZ = -squareSize/2 + z*tileSize; if ((x+z)%2 == 0) { glMaterialfv(GL_FRONT, GL_DIFFUSE, GLColor::GREEN); }else{ glMaterialfv(GL_FRONT, GL_DIFFUSE, GLColor::GREEN2); } glBegin(GL_QUADS); glNormal3d(0, 1, 0); glVertex3d(baseX , (z%2==1)&&(x%2==1)?tileHeight:0, baseZ); glVertex3d(baseX+tileSize, (z%2==1)&&(x%2==0)?tileHeight:0, baseZ); glVertex3d(baseX+tileSize, (z%2==0)&&(x%2==0)?tileHeight:0, baseZ+tileSize); glVertex3d(baseX, (z%2==0)&&(x%2==1)?tileHeight:0, baseZ+tileSize); glEnd(); } } } void displayHall(){ glPushMatrix(); glTranslatef(0, -4, 0); displayFloor(); glPopMatrix(); glPushMatrix(); glRotatef(180, 0, 0, 1); glTranslatef(0, -4, 0); displayFloor(); glPopMatrix(); glPushMatrix(); glRotatef(90, 0, 0, 1); glTranslatef(0, -5, 0); displayFloor(); glPopMatrix(); glPushMatrix(); glRotatef(-90, 0, 0, 1); glTranslatef(0, -5, 0); displayFloor(); glPopMatrix(); } void display(){ GLObject::display(); glMaterialf(GL_FRONT, GL_AMBIENT, 0.1); glMaterialf(GL_FRONT, GL_SPECULAR, 0.1); glMaterialf(GL_FRONT, GL_SHININESS, 0.0); glPushMatrix(); glTranslatef(0, 0, 18); displayHall(); glPopMatrix(); for (int i=1; i<30; i+=2) { glPushMatrix(); glTranslatef(0, 0, -18*i+4*i); displayHall(); glPopMatrix(); } } }; #endif
更新履歴
- 2010/7/22 大学一年次にプログラミングの授業の課題のために作成
- 2010/8/19 公開
- 2010/8/22 msvcr100.dll を添付して再アップロード
- 2011/2/22 MediaWikiに移転
- 2011/9/4 大学二年次の夏休みにオブジェクト指向らしくリファクタリングし,OpenCVを使ったヘッドトラッキング機能を加えて再公開