TankAction

From Usipedia
Jump to: navigation, search
TankAction.png
TankActionHeadTracking.png

私が所属しているプログラミングサークル(MCC)では毎年学祭で来場者の小学生向けにゲームを作って遊んでもらっています. 今回はOpenGLとOpenCVの練習もかねて,3D対戦シューティングゲームを作成しました. オブジェクト指向の流儀に則って開発したため拡張が簡単に出来ます(けど急ピッチで開発したためアクセス指定子はいい加減です).

自機の手動操作と自律操作(敵機と同じアルゴリズムで勝手に戦ってくれます)を動的に切り替えたり,敵の数を動的に増やしたり減らしたり出来る機能が特徴的です. また,OpenCVの顔認識機能とWebカメラを使った(擬似的な)ヘッドトラッキングにも対応しています.

Contents

ダウンロード

Windows/Mac用の実行ファイル,ソースコード,ライブラリなどが同梱されています.

実行ファイルは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

ヘッドトラッキングのサンプル

HeadTrackingSampleをMac OSで実行している様子

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を使ったヘッドトラッキング機能を加えて再公開
Namespaces
Variants
Views
Actions
Categories