Bullet学习之你的Hello World

本文档介绍了Bullet物理引擎的初步学习,包括创建一个基于DemoApplication的BasicDemo类,涉及碰撞配置、调度器、宽相、解算器等关键组件的初始化。核心函数initPhysics()和exitPhysics()用于物理场景的设置和清理。通过clientMoveAndDisplay()函数进行模拟步进和显示,同时提供displayCallback()、clientResetScene()等回调函数。文章强调Bullet的文档阅读及实践的重要性,通过示例代码帮助理解物理引擎的基本用法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

写在所有的内容之前:如果您的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碰撞调度器

       //宽相的那个概念的东西。不理解

       //设置约束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_dynamicsWorldDemoApplication类的一个保护成员。

       //之前在头文件里,我自定义了一个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); //removeobj,这个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的过程,记录下来,如果需要呢,以后还可以温故而知新,或许上面为什么有问题也解决了~

以上!

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值