注意:本文标注为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的研究。