首页 > 代码库 > PhysX3 学习笔记2.5

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的研究。

PhysX3 学习笔记2.5