PhysX3 學習筆記2.5

注意:本文標註爲2.5的原因是因爲主要涉及OpenGL概念複習,並沒有完全涉及到PhysX物理模擬

上回說到,用PhysX搭建了一個基本的空框架。

今天要說的主要內容是,PhysX和OpenGL中的座標轉換。聲明一下有時候我會創建一些之後要用的變量,有可能一開始會看不懂,但堅持下去

最後你會明白的。

最後的結果將是屏幕中出現一個PhysX座標系的座標軸。在這過程中我們將會使用到Plane

Plane將空間分爲上下兩部分,所有在Plane之上的物體都會和他發生碰撞。


1,創建我們的可視空間

添加一個新的類庫

#include <foundation\Mat33.h>

這個類庫中有一個函數,是用來將變換矩陣從PhysX中轉換到OpenGl的。因爲OpenGL和PhysX的世界座標不太相同

左側的OpenGL座標系爲笛卡爾座標系(從左向右x正,從內向外z正,從下向上y正)

右側PhysX中x和y的正軸方向和OpenGL相反。

關於這個函數的具體實現,我們下一篇文章中會提及。


創建可見的牆體

我們將使用Plane來創建上述物體。

我們先修改一下我們的全局變量:

#define MAX_PATH 16384
char buffer [MAX_PATH]

int start_time = 0,total_frames = 0,state = 1,oldX =0,oldY =0;
float fps =0,rX =0,rY =50,dist =0;
const int WINDOW_WIDTH = 800,WINDOW_HEIGHT =600,OBJ_NUM = 130;
const float Gravity = -9.8;

typedef GLfloat point3[3];
point3 planeVertice[4] = {{-10,0,10},{-10,0,-10},{10,0,-10},{10,0,10}};
</pre>接下來,我們要使用這些函數來繪製一個網格(作爲地面)<p></p><p></p><pre name="code" class="cpp">void drawGrid(int n)
{
	glBegin(GL_LINES);
	glColor3f(0.7f,0.7f,0.7f);

	for(int i = -n;i<=n;i++)
	{
		glVertex3f((float)i,0,(float)-n);
		glVertex3f((float)i,0,(float)n);

		glVertex3f((float)-n,0,(float)i);
		glVertex3f((float)n,0,(float)i);
	}
	glEnd();
}
很容易理解,畫了一個20x20的網格地面


有了地面不妨再畫個牆:

void drawPlane()
{
	glBegin(GL_POLYGON);

	glNormal3f(0,1,0);
	glVertex3fv(planevertice[0]);
	glVertex3fv(planevertice[1]);
	glVertex3fv(planevertice[2]);
	glVertex3fv(planevertice[3]);
	glEnd();

}


2,調整視角

用我們的新函數來完善一下我們的onRender函數

void onRender()
{
        GLdouble viewer[3] = {20*sin(0.0),20,20*cos(0.0)};

	total_frames ++;
	int current = glutGet(GLUT_ELAPSED_TIME);
	if((current-start_time)>1000)
	{
		float elapsed_time = float(current - start_time);
		fps = ((total_frames*1000.0f)/elapsed_time);
		start_time=current;
		total_frames =0;
	}


	if(gloable_scene)
		StepPx();

	glClearColor(0.1,0.1,0.1,1.0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	glTranslatef(0,0,dist);
	gluLookAt(viewer[0],viewer[1],viewer[2],0,0,0,0,1,0);
	glRotatef(rX,1,0,0);
	glRotatef(rY,0,1,0);

	drawGrid(10);

	glPushMatrix();
	glTranslatef(0,10,10);
	glRotatef(90,1,0,0);
	drawPlane()
 	glPopMatrix();

	glPushMatrix();
	glTranslatef(0,10,-10);
	glRotatef(-90,1,0,0);
	drawPlane()
	glPopMatrix();

	glPushMatrix();
	glTranslatef(0,10,0);
	glRotatef(90,0,1,0);
	glRotatef(-90,1,0,0);
	drawPlane()
	glPopMatrix();

	glutSwapBuffers();
}

在onRender中,我們使用了glulookAt函數進行了視角轉換

該函數原型爲

void gluLookAt(GLdouble eyex,GLdouble eyey,GLdouble eyez,GLdouble centerx,GLdouble centery,GLdouble centerz,GLdouble upx,GLdouble upy,GLdouble upz);
它定義一個視圖矩陣,並與當前矩陣相乘。

其中

第一組eyex, eyey,eyez   相機在世界座標的位置
第二組centerx,centery,centerz 相機鏡頭對準的物體在世界座標的位置
第三組upx,upy,upz 相機向上的方向在世界座標中的方向

你把相機想象成爲你自己的腦袋:
第一組數據就是腦袋的位置
第二組數據就是眼睛看的物體的位置
第三組就是頭頂朝向的方向(因爲你可以歪着頭看同一個物體)

在繪製OpenGL圖形的時候我們又用到了glTransform,和glRotate,前者比較好理解,讓當前的座標以(x,y,z)向量移動。

後者void glRotatef(GLfloat angle,  GLfloat x,  GLfloat y,  GLfloat z)可以理解爲繞着向量(x,y,z)旋轉angle值

實際上就是使當前矩陣乘上下面的變換矩陣

其中,c = cos(angle),s = sin(angle),並且||(x, y, z)|| = 1

如果不太明白,我們已繪製第一個Plane爲例。

translate之後座標變爲(x,y+10,z+10);

rotate矩陣爲(隨手寫的,懶得用電腦繪製..


這樣大概就能明白了吧。同時,因爲Plane的繪製函數告訴我們,Y正軸方向就是plane的上面,也就是會產生碰撞的一面。


鼠標控制器

每次調整視角都要改代碼多麻煩,我們乾脆新建一個鼠標控制視角變換的函數。

void onReshape(int nw,int nh)
{
	glViewport(0,0,nw,nh);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(50,nw/nh,0.1f,1000.0f);
	glMatrixMode(GL_MODELVIEW);
}

void Mouse(int button,int s,int x,int y)
{
	if(s == GLUT_DOWN)
	{
		oldX =x;
		oldY =y;
	}
	if(button == GLUT_RIGHT_BUTTON)
		state = 1;
	else
		state = 0;
	
}

void Motion(int x, int y)
{
	if(state ==1 )
	{
		rX += (x-oldX)/5.0f;
		rY += (y-oldY)/5.0f;
	}

	oldY =y;
	oldX =x;
	glutPostRedisplay();
}

3,修正函數

爲了讓上述的新功能生效,我們需要修改一下main函數

<pre name="code" class="cpp">void main(int argc,char**argv)
{
	atexit(onShutdown);
	glutInit(&argc,argv);

	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
	glutInitWindowSize(WINDOW_WIDTH,WINDOW_HEIGHT);
	glutCreateWindow("PxTest");
	
	glutDisplayFunc(onRender);
	glutIdleFunc(onIdle);
	
	glutReshapeFunc(onReshape);
	glutMouseFunc(Mouse);
	glutMotionFunc(Motion);

	InitPx();

	glEnable(GL_DEPTH_TEST);
	glDepthMask(GLU_TRUE);
	glutMainLoop();
}

同時給我們的InitPx添加少許的容錯機制、同時吧gravity的參數改爲全局變量

void InitPx()
{
	PxFoundation *foundation = PxCreateFoundation(PX_PHYSICS_VERSION,gloable_allocator,gloable_errorcallback);
	if(!foundation)
		cerr<<"PxCreateFoundation failed!"<<endl;

	PxDirector = PxCreatePhysics(PX_PHYSICS_VERSION, *foundation,PxTolerancesScale() );
	if(PxDirector == NULL)
	{
		cerr<<"Error creating PhysX3 device."<<endl;
		cerr<<"Exiting..."<<endl;
		exit(1);
	}

	PxInitExtensions(*PxDirector);
	if(!PxInitExtensions(*PxDirector))
		cerr<<"PxInitExtensions failed!"<<endl;
	//創建場景
	PxSceneDesc scene_desc(PxDirector->getTolerancesScale());

	scene_desc.gravity= PxVec3(0.0f,Gravity,0.0f);

	if(!scene_desc.cpuDispatcher)//調度員不存在
	{
		PxDefaultCpuDispatcher *cpu_dispatcher = PxDefaultCpuDispatcherCreate(1);
		scene_desc.cpuDispatcher = cpu_dispatcher;
	}
	if(!scene_desc.filterShader)
		scene_desc.filterShader = gloable_filtershader;

	gloable_scene = PxDirector->createScene(scene_desc);
	if(!gloable_scene)
		cerr<<"createScene failed!"<<endl
}

這樣修正完全之後,我們就可以運行了,不出意外的話,你應該看到下面的工字形牆體


4,添加PhysX座標

是不是感覺很好玩?接下來我們要做的事情就是在這個世界中添加座標軸(由於PhysX的座標和OpenGL不一樣,所以有直觀的可視化座標有利於理解)

首先,爲了給座標騰出空間,我們在onRender函數中修改繪製第三個plane的代碼如下

glPushMatrix();
	glTranslatef(0,10,0);
	glRotatef(90,0,1,0);
	glRotatef(-90,1,0,0);
	glTranslatef(0,10,0);//new code
	drawPlane();
	glPopMatrix();

新建繪製座標的函數
void drawAxe()
{
	glPushMatrix();
	glColor3f(0,0,1);
	glPushMatrix();
	glTranslatef(0,0,0.8f);
	glutSolidCone(0.0325,0.2, 4,1);	
	glPopMatrix();
	glutSolidCone(0.0225,1, 4,1);
        //Z軸

	glColor3f(1,0,0);
	glRotatef(90,0,1,0);
	glPushMatrix();
	glTranslatef(0,0,0.8f);
	glutSolidCone(0.0325,0.2, 4,1);
	glPopMatrix();
	glutSolidCone(0.0225,1, 4,1);
        //X軸

	glColor3f(0,1,0);
	glRotatef(90,-1,0,0);
	glPushMatrix();
	glTranslatef(0,0, 0.8f);
	glutSolidCone(0.0325,0.2, 4,1);
	glPopMatrix();
	glutSolidCone(0.0225,1, 4,1);
        //Y軸
	glPopMatrix();
}
在onRender中調用,就可顯示三個座標了。

其中用到了glutSolidCone函數。讀者有可能對我標註的ZXY軸感到困惑,沒關係,看了函數原型就能理解

該函數原型爲

GLsolidCone(radius,height,slices,stacks);
生成一個底座在xy平面,沿着Z軸生長的圓錐。後兩個參數分別爲繞z軸的切割數和沿着Z軸的切割數。

考慮到PhysX座標並不是笛卡爾座標系,所以分別生成了ZXY軸。

當然,並不是所有人的空間想象能力都那麼好,有時候筆者自己都會搞糊塗(爲了寫這個教程,筆者花了整個週日複習了工程線性代數....)所以我們乾脆直接將XYZ標註在座標旁邊。


我們需要新建一個函數用於顯示文字

void RenderSpacingString(int x,int y,int spacing,void *font,char *string)
{
	char *c;
	int x1 =x;
	for(c =string;*c !='\0';c++)
	{
		glRasterPos2i(x1,y);
		glutBitmapCharacter(font ,*c);
		x1 = x1+glutBitmapWidth(font, *c) +spacing;
	}
}

當然這個只能設置平面文字,當做GUI text使用(讀者可以再onRender中嘗試一下),想要這些文字在三維空間中顯示,還需要設置正交和透視

void SetOrthoForFont()
{
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	gluOrtho2D(0, WINDOW_WIDTH, 0, WINDOW_HEIGHT);
	glScalef(1, -1, 1);
	glTranslatef(0, -WINDOW_HEIGHT, 0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

void ResetPerspectiveProjection()
{
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
}

修改drawAxe函數,我們以Z軸爲例
void drawAxe()
{
	glPushMatrix();
	glColor3f(0,0,1);
	glPushMatrix();
	glTranslatef(0,0,0.8f);
	glutSolidCone(0.0325,0.2, 4,1);
	glTranslatef(0,0.0625,0.225f);
	RenderSpacingString(0,0,0,GLUT_BITMAP_HELVETICA_10, "Z");
	glPopMatrix();
	glutSolidCone(0.0225,1, 4,1);
}

最後不要忘了在onRender中,調用這兩個函數
drawAxe();
SetOrthoForFont();
ResetPerspectiveProjection();
//在此之前添加
glutSwapBuffers();

運行後得到了下圖


好嘞,今天的教程就到這裏結束了。下次我們將學習如何在場景中添加奇奇怪怪的Actors。

我是妖哲,微博@捲毛的呈秀波,咱們下期再見。


感謝http://blog.csdn.net/wangyuchun_799/article/details/7786031 關於GL的研究。

發佈了40 篇原創文章 · 獲贊 6 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章