如何使用Bullet物理引擎 碰撞检测

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何使用Bullet物理引擎 碰撞检测相关的知识,希望对你有一定的参考价值。

我已经创建好了btRigidBody并且设置了碰撞组和掩码
mBulletWorld->addRigidBody(aRigidBody,group,mask);
应该实现哪个碰撞后的回调函数?!!!谢谢!

Bullet教程: Hello World 实例

更多信息请关注 物理引擎中文社区http://www.physicsengine.org
欢迎加qq群 52821727 讨论更多关于Bullet的东西
这篇文章里我们将尽可能简单的向你展示怎么使用Bullet, 怎样初始化Bullet, 设置一个动力学世界, 还有一个球落向地表 这个对鉴别你的build是否成功非常有用并且也能够让你快速的学习到Bullet的API. 首先,我们假设你的Bullet已经正确安装并且正确设置了Bullet的include路径(例如. /usr/local/include/bullet) 确保能连接到正确的lib. 否则请参阅Installation安装. 如果你用的是gcc来编译,请确保你的静态库反序,就是说. dynamics, collision, math.

初始化程序
以一个标准的hello world程序开始:

[cpp] view plaincopy
#include <iostream>
int main ()

std::cout << "Hello World!" << std::endl;
return 0;


创建世界
现在我们要添加一个子弹(Bullet)模拟. 首先写入以下语句:
#include <btBulletDynamicsCommon.h>
我们想把btDiscreteDynamicsWorld 实例化但是在做此之前我们还需要解决一些其他事情. 对于一个“hello world”例子来说它太复杂我们并不需要. 但是,为了能更符合我们自己的工程, 他们可以用来微调(fine-tuning)模拟环境.
我们需要指出使用什么样的 Broadphase algorithm(宽相算法). 选择什么样的泛型算法很总要,如果有许多刚体在绘制场景里, since it has to somehow check every pair which when implemented naively(天真) is an O(n^2) problem.
宽相(broadphase)使用 传统的近似物体形状并且被称之为代理.我们需要提前告诉子弹最大的代理数, 所以才能很好的分配内存避免浪费. 下面就是世界里任何时候的最大刚体数.
int maxProxies = 1024;
一些 broadphases 使用特殊的结构要求世界的尺度提前被告知, 就像我们现在遇到的情况一样. 该broadphase可能开始严重故障,如果离开这个物体体积. 因为 the AxisSweep broadphase quantizes 空间基于我们使用的整个空间的大小, 您想这差不多等于你的世界.
使它小于你的世界将导致重大问题, 使它大于你的世界将导致低劣的性能.这是你程序调整的一个简单部分, 所以为了确保数字的正确多花点时间也不防.
在这个例子中,世界从起点开始延伸10公里远。

[cpp] view plaincopy
btVector3 worldAabbMin(-10000,-10000,-10000);
btVector3 worldAabbMax(10000,10000,10000);

这个broadphase是我们将要使用的, 这个执行的是扫描和裁剪, 这里可以看到更多解释Broadphase .

[cpp] view plaincopy
btAxisSweep3* broadphase = new btAxisSweep3(worldAabbMin,worldAabbMax,maxProxies);

该broadphase是一个极好的空间以消除不应碰撞的成队物体. 这是为了提高运行效率.
您可以使用碰撞调度注册一个回调,过滤器重置broadphase代理,使碰撞系统不处理系统的其它无用部分
. 更多信息请看 Collision Things.
碰撞配置可以让你微调算法用于全部(而不是不是broadphase )碰撞检测。这个方面现在还属于研究阶段

[cpp] view plaincopy
btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration();
btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration);

我们还需要一个"solver". 这是什么原因导致物体进行互动得当,考虑到重力,游戏逻辑等的影响,碰撞,会被制约。
它工作的很好,只要你不把它推向极端,对于在任何高性能仿真都有瓶颈有一些相似的可以线程模型:

[cpp] view plaincopy
btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver;

终于我们可以初始化了世界了:

[cpp] view plaincopy
btDiscreteDynamicsWorld* dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration);

很明显我们把重力方向设置成了Y轴的负方向,即Y轴是像上的

[cpp] view plaincopy
dynamicsWorld->setGravity(btVector3(0,-10,0));

子弹的政策是“谁分配,也删除” 记住,必须符合这样的结果
在main()后记的删除.
我们提供了一个通用的结果. 代码如下:

[cpp] view plaincopy
#include <btBulletDynamicsCommon.h>
#include <iostream>

int main ()
std::cout << "Hello World!" << std::endl;

// Build the broadphase
int maxProxies = 1024;
btVector3 worldAabbMin(-10000,-10000,-10000);
btVector3 worldAabbMax(10000,10000,10000);
btAxisSweep3* broadphase = new btAxisSweep3(worldAabbMin,worldAabbMax,maxProxies);

// 设置好碰撞属性 和调度
btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration();
btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration);

// 实际上的物理模拟器
btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver;

// 世界.
btDiscreteDynamicsWorld* dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration);

// 这里做一些你想做的事

// 作为一个好的编程习惯 做好删除工作
delete dynamicsWorld;
delete solver;
delete dispatcher;
delete collisionConfiguration;
delete broadphase;

return 0;


碰撞包围体
我们将创造一个接地平面[静态刚体] ,和一个球体,将属于在地上[动态刚体] 。每个刚体需要参考碰撞包围体. 碰撞包围体只解决碰撞检测问题, 因此没有质量,惯性,恢复原状等概念. 如果您有许多代理,使用相同的碰撞形状[例如每飞船模拟是一个5单元半径范围]。这是个好做法,只有一个子弹形状的碰撞,并分享它在所有这些代理. 但是我们这里的两个刚体形状都不一样,所以他们需要各自的shape.
地面通常是向上的并且里原始点1米的样子. 地面会和远点交叉,但子弹不允许这样做,
因此,我们将抵消它的1米和用来弥补,当我们把刚体设置好以后。

[cpp] view plaincopy
btCollisionShape* groundShape = new btStaticPlaneShape(btVector3(0,1,0),1);

我们将让它从天上掉下来,它是一个球体,半径为1米.

[cpp] view plaincopy
btCollisionShape* fallShape = new btSphereShape(1);

这里需要做碰撞形状的清理工作.

刚体
在,我们可以添加形状的碰撞到我们的现场,并将它们定位.
让我们先初始化地面. 它的方向是特定的, 子弹的四元数形式 x,y,z,w . 位置在地面下一米, 将要补充一米我们不得不做的. 运动状态在这里可以得到详细的说明:MotionStates

[cpp] view plaincopy
btDefaultMotionState* groundMotionState =
new btDefaultMotionState(btTransform(btQuaternion(0,0,0,1),btVector3(0,-1,0)));

在第一个和最后一个参数,在下面的构造函数中是质量和地表的惯性. 由于地面是静止的所以我们把它设置成0. 固定不动的物体,质量为0 -他是固定的.

[cpp] view plaincopy
btRigidBody::btRigidBodyConstructionInfo
groundRigidBodyCI(0,groundMotionState,groundShape,btVector3(0,0,0));
btRigidBody* groundRigidBody = new btRigidBody(groundRigidBodyCI);

最后我们把地面加到世界中:

[cpp] view plaincopy
dynamicsWorld->addRigidBody(groundRigidBody);

新增下跌领域非常相似。我们将其置于50米以上的地面.

[cpp] view plaincopy
btDefaultMotionState* fallMotionState = new btDefaultMotionState(btTransform(btQuaternion(0,0,0,1),btVector3(0,50,0)));

由于它是动态刚体,我们将给予质量1公斤。我不记得如何计算一个球体的惯性,但是,这并不重要,因为子弹提供它的实现

[cpp] view plaincopy
btScalar mass = 1;
btVector3 fallInertia(0,0,0);
fallShape->calculateLocalInertia(mass,fallInertia);

现在,我们可以建造刚体只是像以前一样,并把它加到世界中:

[cpp] view plaincopy
btRigidBody::btRigidBodyConstructionInfo fallRigidBodyCI(mass,fallMotionState,fallShape,fallInertia);
btRigidBody* fallRigidBody = new btRigidBody(fallRigidBodyCI);
dynamicsWorld->addRigidBody(fallRigidBody);

一个快速的解释btRigidBody::btRigidBodyConstructionInfo是为了; 物体的构建是通过某些参数的. 这是通过一个特殊的结构实现的。 该部分的btRigidBodyConstructionInfo被复制到物体当你建造的时候,并只用于在初始化的时候. 如果你想创建几千个属性一样的物体, 你只需要建立一个btRigidBodyConstructionInfo, 并通过它创建所有的.

开始模拟
这就是有趣的开始。我们会加强模拟200倍,间隔60赫兹. 这使它有足够的时间降落的地面上. 每一步, 我们都会打印出它离地面的高度.
这stepSimulation 在做你所期待, 不过他的接口确实很复杂. 读Stepping The World 以获得更多消息.
进后,我们审查的状态下降领域.位置和方向都封装在btTranform对象,我们摘录下降领域的运动状态. 我们只关心位置,我们退出变换getOrigin ( ) 。然后,我们打印y组成部分的立场载体.

[cpp] view plaincopy
for (int i=0 ; i<300 ; i++)

dynamicsWorld->stepSimulation(1/60.f,10);

btTransform trans;
fallRigidBody->getMotionState()->getWorldTransform(trans);

std::cout << "sphere height: " << trans.getOrigin().getY() << std::endl;


这应该产生一个输出看起来像这样的东西:
sphere height: 49.9917
sphere height: 49.9833
sphere height: 49.9722
sphere height: 49.9583
sphere height: 49.9417
sphere height: 49.9222
sphere height: 49.9
...
sphere height: 1
sphere height: 1
sphere height: 1
sphere height: 1
sphere height: 1
看起来不错迄今。如果你图这对输出迭代次数,你就会得到这个:

这个球体开始于地表的一米处. 这是因为取的是几何中心并且它的半径为1米. 这个球刚开始会有一个大的反弹然后渐渐的减缓弹起高度.
这是可以预料的实时物理引擎,但它可以尽量减少,增加频率的模拟步骤
. 试试再说!
现在你可以把这个动态世界代入你的程序 实时绘制出这个球体. 也可以看看其他的 Collision Shapes . 试试一堆盒子 或者圆柱体然后用一个球去扔向他们.

完整代码

[cpp] view plaincopy
#include <iostream>

#include <btBulletDynamicsCommon.h>

int main (void)


btVector3 worldAabbMin(-10000,-10000,-10000);
btVector3 worldAabbMax(10000,10000,10000);
int maxProxies = 1024;
btAxisSweep3* broadphase = new btAxisSweep3(worldAabbMin,worldAabbMax,maxProxies);

btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration();
btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration);

btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver;

btDiscreteDynamicsWorld* dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration);

dynamicsWorld->setGravity(btVector3(0,-10,0));

btCollisionShape* groundShape = new btStaticPlaneShape(btVector3(0,1,0),1);

btCollisionShape* fallShape = new btSphereShape(1);

btDefaultMotionState* groundMotionState = new btDefaultMotionState(btTransform(btQuaternion(0,0,0,1),btVector3(0,-1,0)));
btRigidBody::btRigidBodyConstructionInfo
groundRigidBodyCI(0,groundMotionState,groundShape,btVector3(0,0,0));
btRigidBody* groundRigidBody = new btRigidBody(groundRigidBodyCI);
dynamicsWorld->addRigidBody(groundRigidBody);

btDefaultMotionState* fallMotionState =
new btDefaultMotionState(btTransform(btQuaternion(0,0,0,1),btVector3(0,50,0)));
btScalar mass = 1;
btVector3 fallInertia(0,0,0);
fallShape->calculateLocalInertia(mass,fallInertia);
btRigidBody::btRigidBodyConstructionInfo fallRigidBodyCI(mass,fallMotionState,fallShape,fallInertia);
btRigidBody* fallRigidBody = new btRigidBody(fallRigidBodyCI);
dynamicsWorld->addRigidBody(fallRigidBody);

for (int i=0 ; i<300 ; i++)
dynamicsWorld->stepSimulation(1/60.f,10);

btTransform trans;
fallRigidBody->getMotionState()->getWorldTransform(trans);

std::cout << "sphere height: " << trans.getOrigin().getY() << std::endl;


dynamicsWorld->removeRigidBody(fallRigidBody);
delete fallRigidBody->getMotionState();
delete fallRigidBody;

dynamicsWorld->removeRigidBody(groundRigidBody);
delete groundRigidBody->getMotionState();
delete groundRigidBody;

delete fallShape;

delete groundShape;

delete dynamicsWorld;
delete solver;
delete collisionConfiguration;
delete dispatcher;
delete broadphase;

return 0;
参考技术A 很多时候,在游戏中会遇到穿墙bug,那是因为没有使用CCD技术.
所谓CCD就是连续的碰撞检测,与之对应的是离散点碰撞检测..
这两种的碰撞检测技术的去别:
离散点的碰撞检测是指定某一时刻T的两个静态碰撞体,看它们之间是否交迭,如果没有交迭则返回它们最近点的距离,如果交迭则返回交迭深度,交迭方向等。
连续碰撞检测则是分别指定在T1、T2两个时刻两个碰撞体的位置,看它们在由T1运动到T2时刻的过程中是否发生碰撞,如果碰撞则返回第一碰撞点的位置和法线。连续碰撞检测是最为自然的碰撞检测,可以大大方便碰撞响应逻辑的编写,可以很容易避免物体发生交迭或者穿越。离散点的碰撞检测则没有那么友好,当检测到碰撞时两个物体已经发生了交迭,如果其中有三角形网格对象那么已经有许多三角形发生了交迭,如何将两个交迭的对象分开并按合理的方式运动是一个挑战。虽然连续碰撞检测是最自然的方式,但它的实现非常复杂,运算开销也很大,所以目前大部分成熟的物理引擎和碰撞检测引擎还是采用了基于离散点的碰撞检测,为了避免物体交迭过深或者彼此穿越,它们都要采用比较小的模拟步长。由于碰撞检测引擎的复杂性和对效率的高要求,我们应该尽量使用目前成熟的完整引擎,而不是自己去开发。
很幸运的是Bullet在2.7x版本以后就支持CCD技术.在Bullet中使用CCD技术比较简单
btRigidBody提供了两个函数用于实现CCD技术

setCcdMotionThreshold();
setCcdSweptSphereRadius();
通过刚体对象调用这两个函数,并且设置相应的参数就可以使用CCD技术了
比如:当物体每秒是移动速度超过1米时,将setCcdMotionThreshold();的参数设置为1,默认为0表述不使用CCD

body->setCcdMotionThreshold(1);

同时根据刚体碰撞形状的直径设置ccd扫描球
btVector3 pos;
btScalar radiu;
Shape->getBoundingSphere(pos,radiu);

body->setCcdSweptSphereRadius(radiu*0.2);

注意扫描球的直径要小于刚体的包围球的直径

通过以上设置后就不会出现物体在一些特殊的时候发生穿墙的事情了
参考技术B 很多时候,在游戏中会遇到穿墙bug,那是因为没有使用CCD技术.
所谓CCD就是连续的碰撞检测,与之对应的是离散点碰撞检测..
这两种的碰撞检测技术的去别:
离散点的碰撞检测是指定某一时刻T的两个静态碰撞体,看它们之间是否交迭,如果没有交迭则返回它们最近点的距离,如果交迭则返回交迭深度,交迭方向等。
连续碰撞检测则是分别指定在T1、T2两个时刻两个碰撞体的位置,看它们在由T1运动到T2时刻的过程中是否发生碰撞,如果碰撞则返回第一碰撞点的位置和法线。连续碰撞检测是最为自然的碰撞检测,可以大大方便碰撞响应逻辑的编写,可以很容易避免物体发生交迭或者穿越。离散点的碰撞检测则没有那么友好,当检测到碰撞时两个物体已经发生了交迭,如果其中有三角形网格对象那么已经有许多三角形发生了交迭,如何将两个交迭的对象分开并按合理的方式运动是一个挑战。虽然连续碰撞检测是最自然的方式,但它的实现非常复杂,运算开销也很大,所以目前大部分成熟的物理引擎和碰撞检测引擎还是采用了基于离散点的碰撞检测,为了避免物体交迭过深或者彼此穿越,它们都要采用比较小的模拟步长。由于碰撞检测引擎的复杂性和对效率的高要求,我们应该尽量使用目前成熟的完整引擎,而不是自己去开发。

子弹物理引擎,如何冻结物体?

【中文标题】子弹物理引擎,如何冻结物体?【英文标题】:Bullet physics engine, how to freeze an object? 【发布时间】:2011-03-02 04:43:15 【问题描述】:

使用 Bullet 2.76 我试图冻结一个对象(刚体),以便它立即停止移动,但仍会对碰撞做出响应。

我尝试将它的激活状态设置为 DISABLE_SIMULATION,但它对其他对象几乎不存在。此外,如果对象在禁用时与它“碰撞”,就会开始发生奇怪的事情(对象从静态物体中坠落等)

我想,暂时将其转换为静态刚体是可行的,但在 Bullet 方面是否有现有的“本机”方法来实现这一点?

编辑:有没有办法关闭特定对象的重力?

【问题讨论】:

【参考方案1】:

文档有点欠缺,但可以假设以下方法可用于禁用特定身体的重力:

void btRigidBody::setGravity(const btVector3 &acceleration)

【讨论】:

就是这样!在碰撞等之后必须注意停用,但它有效!完美的!非常感谢!【参考方案2】:

只需将刚体的质量设置为 0,然后它就变成静态的...

http://bullet.googlecode.com/svn/trunk/Demos/HelloWorld/HelloWorld.cpp

【讨论】:

链接已失效。 这里有一个新链接:github.com/bulletphysics/bullet3/blob/master/examples/… 将质量设置为 0 但对我不起作用。【参考方案3】:

btRigidBody 的函数称为 setLinearFactor(x,y,z)setAngularFactor(x,y,z),分别允许您限制沿特定轴的运动和绕特定轴的旋转。使用所有0 作为参数调用这两个函数应该会停止所有运动。使用所有 1 再次调用它们将允许再次移动。

【讨论】:

【参考方案4】:

将激活状态设置为零。这就是对象自然睡眠时发生的情况。重力等在再次唤醒之前不会生效。

rigidBody->setActivationState(0);

然后就像任何沉睡的物体一样,它会在碰撞或你对其施加力时被唤醒。

【讨论】:

那么,如何唤醒它? @MiniGod 直接或通过碰撞施加力。 这意味着我们也可以让它在空中休眠?另外,不幸的是,这似乎没有在 JMonkeyEngine 子弹包装器中公开【参考方案5】:

要让这个方法停止你的actor,你必须在每个更新帧都调用它。

void StopActor()

    m_pRigidBody->setLinearVelocity(btVector3(0,0,0));

【讨论】:

【参考方案6】:

将速度和动量设置为零,并将质量设置为一个非常非常大的数字。

【讨论】:

还不能测试它,但这真的会冻结它在半空中的物体吗? (而且,好吧,把它留在那里?) 如果您使用真实的重力方程,它确实如此:F = G * (m1 * m2) / r^2 但是大多数发动机不会使用这个方程,只会假设重力恒定( F = m * g ),因此您还必须关闭该对象的重力和任何其他恒定力。但是,这应该适用于任何弹力和碰撞引擎(可能使用弹力)。 那么问题来了:如何关闭特定对象的重力(在 Bullet 中)? 很抱歉,我的大部分经验是构建物理引擎,但我没有 Bullet 的具体经验。你能指定哪些力适用于哪些物体/身体吗?有没有配置文件什么的? 我很好奇,所以我阅读了一些关于 Bullet 的文档。看起来它们并不容易修改力。将对象的类型更改为静态可能更容易。

以上是关于如何使用Bullet物理引擎 碰撞检测的主要内容,如果未能解决你的问题,请参考以下文章

ammojs api

ammojs api

紧急求助,请问如何在android 系统中通过ndk内嵌c版bullet物理引擎???主要是如何编译bullet源代码?...

如何在 Bullet 中找到投射光线以避免碰撞的位置?

Godot 物理引擎 2D

Bullet3的一些理解