注意:本文标注为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);它定义一个视图矩阵,并与当前矩阵相乘。
其中
在绘制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()在onRender中调用,就可显示三个坐标了。
{
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();
}
其中用到了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的研究。