您的位置:首页 > Web前端

Bullet学习之你的Hello World

2011-05-19 16:03 225 查看
 

写在所有的内容之前:如果您的E文足够强悍, bullet自带的Bullet
User Manual一定要看一看【哪怕是根本不强悍其实也要如此】。当然了,直接硬生生的去看,对于没有接触过物理引擎的来说,确实是一件痛苦又自虐的事情。不过痛并快乐着嘛!相信在看完本文后,在看user manual的话,应该会更容易一些。学习如果更有目的性,自然会更舒畅。但是,user
manual是一定要看的。而且不推荐看中文翻译。并非贬低那些辛苦的翻译者们,那些翻译的文档当然会对阅读英文文档有些帮助,但是毕竟原文中有很多翻译者无法精确表述的部分。
 
Bullet是一个很有意思的物理引擎。之所以有意思,是因为我不会,又听说相对简单。不管怎么样,这是一篇学习记录。初学乍练,也不会回答任何问题。只是想把bullet中最简单的创建过程记录下来。错误是必然的,可以说是肯定有的。毕竟是第一次接触。以后检验吧!
为了使得我们做出来的东西能看得见摸得着【事实上真的可以摸到哦,用鼠标】,Bullet提供了一些学习用的类:DemoApplication,是用glut实现的各种功能。包括窗口啦,键盘鼠标响应啦,甚至还有纹理与阴影光照。
所以说,我们可以首先创建一个自己的类,名字暂时叫做BasicDemo【因为人家给的例子叫这个名字…既然是人家的代码…就要保持高度的还原度嘛】,公有继承GlutDemoApplication类。
BasicDemo类中,首先定义一些私有成员以及公有成员函数,具体的请看如下【因为详细解释都在代码里面。这里还要多嘴一句,#pragma once这句一定要有啊,不然的话,嘿嘿。我写代码最喜欢重叠头文件…所以多亏了这句代码帮忙了】:

#pragma once
#include <OpenGL/GlutDemoApplication.h>
#include <LinearMath/btAlignedObjectArray.h>
#include <btBulletDynamicsCommon.h>
 
class BasicDemo :
       public GlutDemoApplication
{
private:
       //默认的碰撞设置。包含了一些内存设置,碰撞设置等等。用户可以自定义设置。
       btDefaultCollisionConfiguration* m_collisionConfiguration;
 
       //碰撞调度器。大概是设置碰撞方法的?暂时不是很理解。并行处理的需要参看其他。
       btCollisionDispatcher* m_dispatcher;
 
       //宽相,这个概念不明白。果然物理碰撞一点不懂啊……貌似是很多物理引擎都是用的概念
       //需要好好看看了。这里说btDbveBroadphase是很通用的broadphase
       btBroadphaseInterface* m_overlappingPairCache;
 
       //默认的约束solver【解算器?】并行的solver请参考其他。
       //时序脉冲约束solver,是不是线性的意思?
       btSequentialImpulseConstraintSolver* m_solver;
 
       //这个类提供一种分离刚体的模拟。模拟什么的。
       //其构造函数后面有一些参数,顺次是:
       //dispatcher碰撞调度器
       //宽相的那个概念的东西。不理解
    
4000
   //设置约束solver
       //碰撞设置collisionconfiguration
       //但是这里不能自定义啊,自定义的出不来东西……
//    btDynamicsWorld*
m_dynamicsWorld;
 
       //btAlignedObjectArray是一个类似于vector的存在
       //bullet讲究的是责任分配到家。谁分配内存,谁删除内存
       //只要有可能就要确保在刚体中复用碰撞形体。翻译的真不叫事。还不如看英文。
       //这个vector的作用大概主要是删除内存或者复用的时候很方便吧!
       btAlignedObjectArray<btCollisionShape*>
m_collisionShapes;
 
public:
       BasicDemo(void){}
       ~BasicDemo(void){exitPhysics();}
 
       void initPhysics();
//物理设置
       void exitPhysics();
//撤销物理设置
 
       //这个应该是客户端的移动和显示函数。场景生成一定要调用这个函数
       //虚函数,就不解释了。重载于DemoApplication【貌似在这个类里面是纯虚的吧?】
       virtual void clientMoveAndDisplay();
 
       //这个应该是窗口位置大小发生变化的时候进行回调。
       virtual void displayCallback();
 
       //客户端重置。我猜这个也是纯虚的。不过貌似我猜错了。
       virtual void clientResetScene();
 
       //这个函数大概是为什么预留的。在调试过程中,完全没有遇到用到的时候。
       //在BasicDemo中用法不明。
       static DemoApplication*
Create()
       {
              BasicDemo* demo
= new BasicDemo;
              demo->myinit();
              demo->initPhysics();
              return demo;
       }
};
       上面把头文件的代码列出来了。具体的过程如上。而且写得够详细了吧【虽然错误百出】。这里我简单的叙述一下过程。这里面的核心就是对物理场景进行设置。也就是initPhysics()和exitPhysics()函数是很关键的两个函数。其他的函数主要是负责显示与回调。为了能够对物理场景进行设置,需要首先定义一些物理场景参数,分别是Collision Configuration, dispactcher, boardphase, constraint solver,
Aligned Object Array,以及还有注释掉的Dynamics World。这些成员变量的具体含义可以看上面代码。这些代码的赋值都是在initPhysics()里面进行的。上面的是BasicDemo类的头文件。下面把实现cpp代码给出:
#include "BasicDemo.h"
#include <OpenGL/GlutStuff.h>
#include <stdio.h>
//#include
<btBulletDynamicsCommon.h>
 
void BasicDemo::clientMoveAndDisplay()
{
       glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
 
       //大概是用来获取系统刷新时间还是什么?
       //简单的动态世界不会处理固定时间步进,所以要赋值给一个ms么?
       float ms = getDeltaTimeMicroseconds();
 
       //模拟步进开始
       if (m_dynamicsWorld)
       {
              //开始步进模拟,第一个参数是步进时间
              //第二个参数是最大步进时间maxSubSteps
              //如果第二个参数大于,则第三个看不懂fixtimestep
              m_dynamicsWorld->stepSimulation(ms / 1000000.0f);
 
              //这句话可有可无。但是很有用。调试绘制
              m_dynamicsWorld->debugDrawWorld();
       }
      
       //这个……名字太容易理解了
       renderme();
 
       glFlush();
 
       //DemoApplication中的swap buffer,注意第一个字母小写,区别于Win32的
       swapBuffers();
}
 
//可以看出,下面的回调函数的作用,就是重复上面的绘制操作。
//顺序略有不同。不知可否调换
void BasicDemo::displayCallback()
{
       glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
       renderme();
       if (m_dynamicsWorld)
       {
              m_dynamicsWorld->debugDrawWorld();
       }
       glFlush();
       swapBuffers();
}
 
//bullet的重头戏,进行物理场景设置。信息很多。仔细研究
void BasicDemo::initPhysics()
{
       setTexturing(true);
       setShadows(true);
 
       //DemoApplication类中的函数,用来设置相机位置。
       setCameraDistance(btScalar(50.));
 
       //下面进行的工作就是对之前私有成员进行各种初始化
       //使用默认,分陪内存很少
       m_collisionConfiguration = new btDefaultCollisionConfiguration();
 
       m_dispatcher = new
btCollisionDispatcher(m_collisionConfiguration);
 
       m_overlappingPairCache = new
btDbvtBroadphase();
 
       //总之是分配了一个快速的SIMD的什么高斯算法或者什么的东西
       btSequentialImpulseConstraintSolver* sol = new btSequentialImpulseConstraintSolver;
       m_solver = sol;
 
       //在这里把话说清楚。这里的m_dynamicsWorld是DemoApplication类的一个保护成员。
       //之前在头文件里,我自定义了一个dynamicsWorld变量,但是不能出现图像。
       //看来这是由于如果调用人家DemoApplication里面的函数的话,就必须使用人家的内部的成员啊
       m_dynamicsWorld = new
btDiscreteDynamicsWorld(m_dispatcher, m_overlappingPairCache,
m_solver, m_collisionConfiguration);
 
       //激动人心的时刻啊,就这一句最好懂。
       //设置重力参数。bullet是右手坐标系,y轴在上,和OpenGL一样的。
       m_dynamicsWorld->setGravity(btVector3(0, -10, 0));
 
       //创建基础的刚体形状.从名字来看,应该是一个见方的立方体吧.
       //这里是一个地板。对于第二个值,我实在是感到费解。因为竟然可以变成一种高度的存在?
       btCollisionShape* groundShape
=
              new btBoxShape(btVector3(btScalar(50.),
btScalar(1.), btScalar(50.)));
 
       m_collisionShapes.push_back(groundShape);
 
       //btTransform这个类提供了一种刚体平移旋转的方法,不包含缩放以及裁剪!
       //实际上吧,这个类封装了个私有成员,分别是btScalar类型和btVector3类型
       //所以呢,储存了相应刚体的位置和方向啊。
       btTransform groundTransform;
       groundTransform.setIdentity();
//很好懂,矩阵初值化
       groundTransform.setOrigin(btVector3(0, 0, 0)); //这里应该是设置初始位置吧,一会儿实验一下
 
//这里大概是创建一个刚体。并添加到世界中。我怀疑这里是地面。
       //这里可以使用DemoApplication::localCreateRigidBody。但是demo嘛,学习使用的。
       //这里可以清楚的看到如何创建的这个刚体。
       {
              //查阅了大量的资料终于搞明白了mass的含义
              //mass,质量的意思。一直以为是堆的意思……
              //这里要插入一下,刚体的三种类型:静态刚体,动态刚体,可运动刚体【e文原文貌似更好理解……】
                     //dynamic刚体:)mass>0; 2)每一帧都要对其transform进行更新
                     //static刚体:)mass=0; 2)不能移动,但是可以进行碰撞
                     //kinematic刚体) mass=0; 2)这里实在是不好理解……看原文去吧……
              btScalar mass(0.);
 
              //判断是否是dynamic刚体。刚体质量必须是大于的,世界观不考虑反物质
              bool isDynamic = (mass != 0.f);
             
              //设置惯性,或者是质心?。Inertia是惯性的意思。
              //惯性可以交给btCollisionShape的一些方法计算
              btVector3 localInertia(0,
0, 0);
              if (isDynamic)
              {
                     groundShape->calculateLocalInertia(mass, localInertia);
//查了一下,localInertia是输出。
              }
 
              //MotionState提供了可以对world transform进行同步的实现,字面上就是状态监控
              //关于MotionState的好处大大的有:五条,翻译不能,请看user manual
              //下面这句应该是对groundTransform进行监控吧
              btDefaultMotionState* myMotionState
= new btDefaultMotionState(groundTransform);
              //btRgidBodyConstructionInfo是一个结构体,储存了各种刚体信息,并且有构造函数
              btRigidBody::btRigidBodyConstructionInfo
rbInfo(mass,
myMotionState, groundShape,
localInertia);
              //创建刚体!
              btRigidBody* body
= new btRigidBody(rbInfo);
 
              //将刚体添加到dynamics world中!
              m_dynamicsWorld->addRigidBody(body);
       }
 
//再次创建刚体!
       //由于过程同上,注释从简
       {
              //很容易看出,准备创建球形刚体
              btCollisionShape* colShape
= new btSphereShape(btScalar(1.));
              m_collisionShapes.push_back(colShape);  //放入vector中
 
              btTransform startTransform;
              startTransform.setIdentity();
 
              btScalar mass(1.f);       //质量为,可见是dynamic刚体
 
              bool isDynamic = (mass !=0.f);
 
              btVector3 localInertia(0,
0, 0);
 
              if (isDynamic)
              {
                     colShape->calculateLocalInertia(mass, localInertia);
              }
              startTransform.setOrigin(btVector3(2, 10, -5));
 
              //定义一个MotionState监视Transform的状态
              btDefaultMotionState* myMotionState
= new btDefaultMotionState(startTransform);
              btRigidBody::btRigidBodyConstructionInfo
rbInfo(mass,
myMotionState, colShape,
localInertia);
              btRigidBody* body
= new btRigidBody(rbInfo);
 
              m_dynamicsWorld->addRigidBody(body);
       }
      
}
 
void BasicDemo::clientResetScene()
{
       exitPhysics();
       initPhysics();
}
 
//清理工作在这里进行。要按照和创建/初始化相反的次序进行清理
void BasicDemo::exitPhysics()
{
       //在dynamics world中剔除刚体们,这里的工作是将刚体从world中移走,而不是删除
              //注意这里使用的是递减的方式。使用i++应该会造成某些错误。
              //注意这里删除的技巧。不经意的地方可能隐藏着大BUG!
       for (int i=m_dynamicsWorld->getNumCollisionObjects()-1; i>=0; i--)
       {
              btCollisionObject* obj
= m_dynamicsWorld->getCollisionObjectArray()[i];
              btRigidBody* body
= btRigidBody::upcast(obj);     //这里使obj返回为RigidBody类型了。
              //如果物体并且物体的监控也存在,那么先删除绑定的MotionState
              if (body
&& body->getMotionState())
              {
                     delete body->getMotionState();
              }
              m_dynamicsWorld->removeCollisionObject(obj); //remove掉obj,这个obj当然就是世界里面的物体了
              delete obj;            //这一句千万别忘了,不然内存泄漏的话,说是bug都嫌丢人啊!
       }
       //删除碰撞形体。我觉得也用--是不是比较好?
       for (int i=0; i<m_collisionShapes.size();
i++)
       {
              btCollisionShape* shape
= m_collisionShapes[i];
              m_collisionShapes[i]
= 0;
              delete shape; //同上面delete body,千万别忘了这一句
       }
 
//最后的大删除,将世界啊,约束器啊,宽相啊,调度器啊,碰撞设置啊,一股脑都删除
       delete m_dynamicsWorld;
       delete m_solver;
       delete m_dispatcher;
       delete m_overlappingPairCache;
       delete m_collisionConfiguration;
 
       //最后一句话可有可无:在array离开作用域的时候调用析构函数
       m_collisionShapes.clear();
}
是不是不长~物理场景设置就是这么简单。请把注意力放到initPhysics()和exitPhysics()中。至于剩下的函数,代码简单,是Bullet为了让大家集中注意力放在物理引擎学习上,而特意封装的这么简单。大家要注意,在clientMoveAndDisplay()里面,有一步是stepSimulation,调用这个函数,可以正式对物理场景进行模拟。非常重要的函数。至于其他的,都在代码注释里面写有。可以仔细看一下。
最后就是主函数main函数开始登场了。祭出代码【其实没有一句是我写的,我不过是敲一敲熟悉熟悉,唯独注释是独家的…】:
#include "BasicDemo.h"
#include <OpenGL/GlutStuff.h>
#include <btBulletDynamicsCommon.h>
#include <LinearMath/btHashMap.h>
#include <OpenGL/GLDebugDrawer.h>
static GLDebugDrawer sDebugDraw;
int main(int argc, char** argv)
{
       BasicDemo ccdDemo;
       ccdDemo.initPhysics();
#ifdef CHECK_MEMORY_LEAKS
       ccdDemo.exitPhysics();
#else
       return glutmain(argc, argv,
1024, 600, "My first bullet",
&ccdDemo);
#endif
       return 0;
}
这里就是main主函数了。在这里,我曾经把那个#ifdef等等那些宏给去掉。直接留下return glutmain()函数,结果报错……这个是干啥用的我也不知道……还望有人能指点小弟啊。过程很简单,没必要解释了。
这不过是我自己学习bullet的过程,记录下来,如果需要呢,以后还可以温故而知新,或许上面为什么有问题也解决了~
以上!
 
 
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息