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的过程,记录下来,如果需要呢,以后还可以温故而知新,或许上面为什么有问题也解决了~
以上!
相关文章推荐
- Bullet学习之你的Hello World
- spring mvc学习笔记一:hello world
- rabbitmq学习1:hello world
- Bullet的学习资源(用Doxygen生成API文档)
- Django 开发学习笔记(1)- Hello World
- 学习AspectJ框架(一):AspectJ开发环境搭建与Hello World
- SpringBoot学习之路:02.第一个程序Hello World及项目结构介绍
- 驱动学习,入门基础--Hello World
- jsp基础学习---第一个Hello World
- [LIBGDX学习]LibGDX代码详解(二十六)Bullet
- Nginx学习之一-第一个程序Hello World
- dubbo学习--从demo开始:hello world(3)
- cocos2dx游戏开发学习笔记(四)之Hello World
- Object-c 学习之路一(Hello world)
- ios学习笔记(二)第一个应用程序--Hello World
- LISP学习-开发环境以及hello world
- PHP学习之路之Hello World小程序
- SpringMVC 学习笔记(一) Hello World
- Android开发学习入门Hello World
- Bullet(cocos2dx)学习制作桌球游戏之前期准备