根据Bullet用户手册翻译
第一章 介绍Bullet引擎
Bullet 物理引擎是一款专业的开源碰撞检测、刚体和软体动力学库,使用C++编写。Bullet 物理引擎专注于实时和交互式应用,广泛应用于电子游戏、电影视觉效果以及机器人技术中。该库在 zlib 许可证下免费提供商业使用。
主要特性
代码开源,采用zlib许可证,在所有平台上提供免费商业使用,包括 PLAYSTATION 3、XBox 360、Wii、PC、Linux、Mac OSX、Android 和 iPhone
• 支持离散和连续碰撞检测,包括射线检测和凸形扫掠测试。支持的碰撞形状包括凹多边形网格(Concave meshes)、凸多边形网格(Convex meshes)以及所有基本几何体(如球体、盒子、圆柱体等)。
• 提供快速且稳定的刚体动力学约束求解器,支持车辆动力学、角色控制器(Character Controller)、滑块关节(Slider Joint)、铰链关节(Hinge Joint)、通用六自由度约束(Generic 6DOF Constraints)以及用于布娃娃效果的锥形扭曲约束(Cone Twist Constraints)。
• 支持软体动力学,包括布料、绳索和可变物体。这些软体能够与刚体产生双向交互作用,并提供约束支持。
• 提供原生二进制文件格式 .bullet,以及示例导入器,支持导入 URDF(Unified Robot Description Format)、Wavefront .obj 和 Quake .bsp 文件。
第二章 构建Bullet
windows开发者可以直接从http://bullet.googlecode.com/下载Bullet的zip源码包。
构建方法见 在QT下使用CMAKE外接Bullet物理引擎库-CSDN博客
第三章 Bullet概述
物理引擎的主要任务是执行碰撞检测,解决碰撞及处理其它约束条件,为所有物体提供更新变化后的世界变换(刚体提供质心变换,软体提供顶点变换)。
软件设计
Bullet 旨在设计为可定制和模块化的。开发者可以
• 只使用碰撞检测组件
• 在不使用软刚体动力学组件的情况下,使用刚体动力学组件
• 只使用库中的小部分并以多种方式扩展该库
• 选择使用单精度或双精度版本的库
• 使用自定义内存分配器、连接自己的性能剖析工具或调试绘图工具
集成概述
整个物理流程计算和数据结构都在Bullet的动力学世界表示。默认的动力学世界是btDiscreteDynamicsWorld。
Bullet 允许开发者显式地选择动力学世界中的几个部分,例如广相碰撞检测、窄相碰撞检测(调度器)和约束求解器。
如果你想在你自己的3D程序中使用Bullet,最好按照Helloworld 示例步骤,Helloworld位于examples/HelloWorld。
1.创建一个btDiscreteDynamicsWorld 或者 btSoftRigidDynamicsWorld,这两个类继承于btDynamicsWorld,提供了一个高层接口来管理物理对象和约束,它还实现每帧对所有对象的更新。
2.创建一个btRigidBody对象并将其添加到btDynamicsWorld。要构建btRigidBody或btCollisionObject对象,你需要提供:
质量,正数用于动态移动对象,0用于静态对象
碰撞形状,如盒子、球体、圆锥、凸包或三角形网格
材料属性,如摩擦和恢复系统
3.用stepSimulation每帧更新仿真。
在动力学世界上调用stepSimulation。btDiscreteDynamicsWorld通过插值而不是模拟来自动考虑可变时间步长(对于小时间步)。它使用内部固定时间步60赫兹。stepSimulation将执行碰撞检测和物理模拟。它通过调用btMotionState的setWorldTransform来更新活动对象的世界变换。
基本数据类型和数学库
基本数据类型、内存管理和容器都在src/LinearMath中。
• btScalar
btScalar 是 floating point number 的另一种说法。为了允许以单精度浮点和双精度浮点编译库,我们在整个库中使用 btScalar 数据类型。默认情况下,btScalar 定义为 float 类型。通过定义 BT_USE_DOUBLE_PRECISION(可以在构建系统或 Bullet 源代码中的文件 LinearMath/btScalar.h 的顶部进行定义),btScalar 可以是 double 类型。
• btVector3
使用btVector3可以表示3D位置和向量。btVector3具有3个标量x、y、z分量,但由于对齐和SIMD兼容性的原因,它还有一个未使用的第4个w分量。可以在btVector3上执行许多操作,例如加法、减法以及计算向量的长度。
• btQuaternion和btMatrix3x3
可以使用btQuaternion或btMatrix3x3来表示3D方向和旋转。
• btTransform
btTransform是位置和方向的组合。它可以用于将点和向量从一个坐标空间变换到另一个坐标空间。不允许进行缩放或剪切操作。
Bullet使用右手坐标系: y
(0,0,0) x
z
btTransformUtil、btAabbUtil 提供了用于变换和 AABB 的常见实用函数。
内存管理、对齐、容器
Bullet 提供了默认的内存分配器来处理对齐问题,开发人员可以提供自己的内存分配器。Bullet 中的所有内存分配均使用:
• btAlignedAlloc,允许指定大小和对齐方式
• btAlignedFree,释放由 btAlignedAlloc 分配的内存。
要覆盖默认内存分配器,您可以选择以下选项:
• btAlignedAllocSetCustom 用于您的自定义分配器不支持对齐的情况
• btAlignedAllocSetCustomAligned 可以用来设置您自己的对齐内存分配器。
为了确保结构或类将自动对齐,您可以使用此宏:
• ATTRIBUTE_ALIGNED16(type) variablename 创建一个 16 字节对齐的变量
通常需要维护对象数组。最初 Bullet 库使用 STL std::vector 数据结构来实现数组,但出于移植性和兼容性的原因,我们转而使用自己的数组类。
• btAlignedObjectArray 与 std::vector 非常相似。它使用对齐分配器来保证对齐。它提供了使用快速排序或堆排序对数组进行排序的方法。
性能
为了定位性能瓶颈, Bullet使用层次化的宏来进行性能测量。
• btClock 使用微秒精度来计时。
• BT_PROFILE(section_name) 标记 profiling 段的开始。
• CProfileManager::dumpAll(); 在控制台中输出层次化的性能数据。在模拟步进之后调用此方法。
• CProfileIterator 是一个允许您遍历 profiling 树的类。
注意,profiler 不使用内存分配器,因此在检查内存泄漏或构建最终发行版本时,可能需要禁用它。
通过在 Bullet/src/LinearMath/btQuickProf.h 中定义 #define BT_NO_PROFILE 1,可以关闭 profiling 功能。
调试绘图器
调试时可视化仿真数据结构可能会有所帮助。例如,这可以让你验证物理仿真数据是否与图形数据匹配。此外,缩放问题、不良约束帧和限制也会显现出来。
btIDebugDraw是用于调试绘制的接口类。你可以继承自己的类并实现虚拟方法 drawLine及其他方法。
通过调用 setDebugDrawer将自定义调试绘图器分配给动力学世界来使用它。
然后,你可以选择通过设置调试绘图器的模式来绘制特定的调试功能:
dynamicsWorld->getDebugDrawer()->setDebugMode(debugMode);
每帧中,你可以通过调用以下方法来进行调试绘制:
world->debugDrawWorld();
以下是几种调试模式:
- btIDebugDraw::DBG_DrawWireframe
- btIDebugDraw::DBG_DrawAabb
- btIDebugDraw::DBG_DrawConstraints
- btIDebugDraw::DBG_DrawConstraintLimits
默认情况下,所有对象都会根据调试模式进行可视化。当使用大量对象时,这可能会使显示内容变得杂乱。你可以通过设置特定对象的碰撞标志来禁用其调试绘制:
int f = objects->getCollisionFlags();
ob->setCollisionFlags(f | btCollisionObject::CF_DISABLE_VISUALIZE_OBJECT);
第四章 Bullet碰撞检测
碰撞检测
碰撞检测提供用于最近点(距离和穿透)查询、射线和凸形扫掠测试的算法和加速结构。主要数据结构包括:
• btCollisionObject 表示具有世界变换和碰撞形状的对象。
• btCollisionShape 描述碰撞对象的碰撞形状,例如盒子、球体、凸包或三角形网格。一个碰撞形状可以被多个碰撞对象共享。
• btGhostObject 是一种特殊的 btCollisionObject,适用于快速局部碰撞查询。
• btCollisionWorld 存储所有 btCollisionObject,并提供执行查询的接口。
广相碰撞检测提供加速结构,基于轴对齐包围盒(AABB)重叠快速拒绝物体对。提供了几种不同的广相加速结构:
• btDbvtBroadphase 使用基于 AABB 树的快速动态边界体积层次
• btAxisSweep3 和 bt32BitAxisSweep3 实现增量 3D 扫掠和剪切处理
• btSimpleBroadphase 是一种暴力 brute-force 参考实现。它速度较慢但易于理解,适合用于调试和测试更高级的广相。
广相碰撞检测会将重叠对添加到配对缓存并移除它们。重叠对在时间上是持久的,并且可以缓存诸如先前接触约束力之类的信息,这些信息可用于“热启动”:利用先前的解决方案更快地收敛到约束求解。
碰撞调度程序遍历每一对,根据涉及对象的类型搜索匹配的碰撞算法并执行该算法以计算接触点。
• btPersistentManifold 是用于存储给定对象对之间接触点的接触点缓存。
碰撞形状
BUllet支持多种多样的碰撞形状,还可以添加自定义的。为了最佳性能和质量,选择适合您需求的碰撞形状非常重要。
基本图形
大多数基本形状都围绕其局部坐标系原点:
btBoxShape:由其边长的一半定义的盒子
btSphereShape:由其半径定义的球体
btCapsuleShape:沿Y轴的胶囊体。也包括btCapsuleShapeX/Z
btCylinderShape:沿Y轴的圆柱体。也包括btCylinderShapeX/Z
btConeShape:沿Y轴的圆锥体。也包括btConeShapeX/Z
btMultiSphereShape:多个球体的凸包,可用于通过传递两个球体创建胶囊或其他凸形状。
复合形状
使用 btCompoundShape可将多个凸形可以组合成复合或组合形状。这是一种由凸面子部分(称为子形状)组成的凹面,每个子形状都有其相对于 btCompoundShape 的局部偏移变换。将凹面形状近似为一组凸包并存储在 btCompoundShape 中。你可以通过 btCompoundShape::calculatePrincipalAxisTransform 方法调整质心位置。
凸包形状
Bullet 支持多种方式来表示凸三角形网格。最简单的方式是创建一个 `btConvexHullShape` 对象,并传入顶点数组。在某些情况下,图形网格的顶点数量过多,无法直接作为 `btConvexHullShape` 使用。这时,尝试减少顶点的数量。
凹三角形网格
对于静态世界环境,使用 `btBvhTriangleMeshShape` 表示静态三角形网格是一种非常高效的方式。此碰撞形状从 `btTriangleMesh` 或 `btStridingMeshInterface` 构建内部加速结构。如果不希望在运行时构建树结构,还可以将二叉树序列化到磁盘。请参考示例 `examples/ConcaveDemo` 了解如何保存和加载这种 `btOptimizedBvh` 树加速结构。如果存在多个相同三角形网格但缩放不同的实例,可以使用 `btScaledBvhTriangleMeshShape` 多次实例化 `btBvhTriangleMeshShape`。`btBvhTriangleMeshShape` 可以存储多个网格部分。它在 32 位结构中保留了三角形索引和部分索引,其中 10 位用于部分 ID,剩余的 22 位用于三角形索引。如果需要处理超过 200 万个三角形的情况,则可以将三角形网格拆分为多个子网格,或更改文件 `src/BulletCollision/BroadphaseCollision/btQuantizedBvh.h` 中的默认值 `MAX_NUM_PARTS_IN_BITS`。
凸分解
理想情况下,仅应将凹面网格用于静态艺术作品。否则,应该使用 `btConvexHullShape` 传递网格来构建其凸包。如果单个凸形状不够详细,可以将多个凸部分组合成一个复合对象 `btCompoundShape`。凸分解可用于将凹面网格分解为几个凸部分。请参考示例 `Demos/ConvexDecompositionDemo` 了解如何自动执行凸分解操作。
高度场
Bullet通过btHeightfieldTerrainShape支持特殊情况下的二维凹地形。请参见示例/TerrainDemo以了解其用法。
btStaticPlaneShape
顾名思义,btStaticPlaneShape可以表示一个无限平面或半空间。该形状只能用于静态的、不移动的对象。此形状主要为演示目的而引入。
碰撞形状的比例缩放
某些碰撞形状可以应用局部比例缩放。使用btCollisionShape::setScaling(vector3)。非均匀缩放(每个轴的比例不同)可用于btBoxShape、btMultiSphereShape、btConvexShape、btTriangleMeshShape。统一缩放(使用x值对所有轴进行缩放)可用于btSphereShape。注意,可以通过使用具有1个球体的btMultiSphereShape来创建非均匀缩放的球体。如前所述,btScaledBvhTriangleMeshShape允许以不同的非均匀比例因子实例化btBvhTriangleMeshShape。而btUniformScalingShape允许以不同的比例因子实例化凸形状,从而减少内存占用。
碰撞边距
Bullet使用一个小的碰撞边距来提高碰撞检测的性能和可靠性。最好不要修改默认的碰撞边距;如果非要修改,请确保使用正值:零边距可能会引发问题。默认情况下,此碰撞边距设置为0.04,即如果你的单位是米,则表示4厘米(推荐)。
根据不同的碰撞形状,边距具有不同的含义。通常,碰撞边距会扩展对象,从而产生一个小间隙。为了补偿这一点,某些形状会从实际大小中减去边距。例如,btBoxShape会从半轴长中减去碰撞边距。对于btSphereShape,整个半径即为碰撞边距,因此不会产生间隙,请不要覆盖球体的碰撞边距。对于凸包、圆柱体和圆锥体,边距会添加到对象的范围中,从而导致间隙出现,除非你调整图形网格或碰撞大小。对于凸包对象,有一种方法可以消除由边距引入的间隙,即通过缩小对象来实现。请参见示例/Importers/ImportBsp以了解此高级用法。
碰撞矩阵
对于每对形状类型,Bullet会使用分发器调度一种特定的碰撞算法。默认情况下,整个矩阵填充了以下算法。注意,Convex代表凸多面体、圆柱体、圆锥体和胶囊等GJK兼容的原始形状。GJK代表Gilbert、Johnson和Keerthi,他们是这种凸距离计算算法的作者。它结合了EPA来计算穿透深度。EPA代表Gino van den Bergen的扩展多面体算法。Bullet有自己的GJK和EPA的免费实现。
box | sphere | convex,cylinder cone,capsule | compound | triangle mesh | |
box | box box | spherebox | gjk | compound | concaveconvex |
sphere | spherebox | spheresphere | gjk | compound | concaveconvex |
convex, cylinder, cone, capsule | gjk | gjk | gjk 或 SAT | compound | concaveconvex |
compound | compound | compound | compound | compound | compound |
triangle mesh | concaveconvex | concaveconvex | concaveconvex | compound | gimpact |
第五章 碰撞过滤(选择碰撞)
Bullet提供了三种简便的方法来确保只有特定的对象之间发生碰撞:掩码、广相过滤回调函数以及近回调函数。值得注意的是,基于掩码的碰撞选择机制在工具链中的执行阶段比回调函数更早。简而言之,如果掩码足以满足你的需求,请优先使用它们;因为它们性能更高且使用起来更为简单。
当然,不要仅仅为了追求性能上的微小提升而勉强将不适合的情况塞入基于掩码的选择系统中。
**利用掩码筛选碰撞**
Bullet支持使用位掩码来决定对象之间是否发生碰撞或接收碰撞信息。例如:
int myGroup = 1;
int collideMask = 4;
world->addCollisionObject(object, myGroup, collideMask);
在广相碰撞检测阶段,重叠对被添加到一个配对缓存中,但仅当掩码与另一个对象的组匹配时才会进行处理(需满足`needsBroadphaseCollision`条件)。
bool collides = (proxy0->m_collisionFilterGroup & proxy1->m_collisionFilterMask) != 0;
collides = collides && (proxy1->m_collisionFilterGroup & proxy0->m_collisionFilterMask);
如果你的对象类型超过了掩码可用的32位,或者某些碰撞的启用/禁用依赖于其他因素,你可以注册回调函数来实现自定义逻辑,并仅传递你所需的碰撞:
**利用广相过滤回调筛选碰撞**
一种高效的方法是注册广相过滤回调函数。此回调函数在碰撞管道非常早期的阶段被调用,并阻止不符合条件的碰撞对生成。
struct YourOwnFilterCallback : public btOverlapFilterCallback {
// 返回true表示需要发生碰撞
virtual bool needBroadphaseCollision(btBroadphaseProxy* proxy0, btBroadphaseProxy* proxy1) const {
bool collides = (proxy0->m_collisionFilterGroup & proxy1->m_collisionFilterMask) != 0;
collides = collides && (proxy1->m_collisionFilterGroup & proxy0->m_collisionFilterMask);
// 在此处添加额外逻辑以修改'collides'
return collides;
}
};
然后创建此类的对象并注册此回调函数:
btOverlapFilterCallback* filterCallback = new YourOwnFilterCallback();
dynamicsWorld->getPairCache()->setOverlapFilterCallback(filterCallback);
**利用自定义近回调筛选碰撞**
另一个回调函数可以在窄相阶段注册,此时所有由广相生成的对都会被处理。`btCollisionDispatcher::dispatchAllCollisionPairs`会为每个通过`'btCollisionDispatcher::needsCollision'`测试的对调用此窄相近回调函数。你可以自定义这个近回调函数:
void MyNearCallback(btBroadphasePair& collisionPair, btCollisionDispatcher& dispatcher, btDispatcherInfo& dispatchInfo) {
// 在此处实现你的碰撞逻辑
// 如果你希望物理继续,则调用默认的近回调函数
dispatcher.defaultNearCallback(collisionPair, dispatcher, dispatchInfo);
}
mDispatcher->setNearCallback(MyNearCallback);
**通过继承btCollisionDispatcher自定义碰撞分发**
为了实现对碰撞分发更为精细的控制,你可以从`btCollisionDispatcher`类继承,并重写以下一个或多个方法:
virtual bool needsCollision(btCollisionObject* body0, btCollisionObject* body1);
virtual bool needsResponse(btCollisionObject* body0, btCollisionObject* body1);
virtual void dispatchAllCollisionPairs(btOverlappingPairCache* pairCache, const btDispatcherInfo& dispatchInfo, btDispatcher* dispatcher);
第六章 刚体动力学
简介
刚体动力学建立在碰撞检测模块之上。它增加了力、质量、惯性、速度和约束条件。
- `btRigidBody` 用于模拟具有 6 个自由度的单个运动物体。`btRigidBody` 继承自 `btCollisionObject`,因此它继承了其世界变换、摩擦力和恢复力,并添加了线性和角速度。
- `btTypedConstraint` 是刚体约束的基础类,包括 `btHingeConstraint`、`btPoint2PointConstraint`、`btConeTwistConstraint`、`btSliderConstraint` 和 `btGeneric6DOFconstraint`。
- `btDiscreteDynamicsWorld` 是 `UserCollisionAlgorithm btCollisionWorld` 的容器,用于包含刚体和约束条件。它提供了进行步进模拟的 `stepSimulation` 方法。
- `btMultiBody` 使用广义坐标(或简化坐标)表示刚体层次结构,并采用了连杆体算法。该树形层次结构从固定或浮动基座开始,子身体(也称为链接)通过关节连接:1 自由度回转副(类似于 `btRigidBody` 中的 `btHingeConstraint`)、1 自由度直线副(类似于 `btSliderConstraint`)。
请注意,`btMultiBody` 是最近才引入到 Bullet 物理 SDK 中的,目前仍处于开发阶段。在本文档中,仅讨论基于最大坐标系的 `btRigidBody` 和 `btTypedConstraints`。未来版本将增加一章专门介绍 `btMultiBody`。如果您对 `btMultiBody` 感兴趣,请查看示例浏览器及其源代码位于 `examples/MultiBody` 和 `examples/ImportURDF` 目录中。
静刚体、动刚体与运动刚体
在 Bullet 中,有三种不同类型的物体:
• 动态(移动)刚体:具有正质量, 每一帧的模拟都会更新其世界变换
• 静态刚体:零质量,无法移动,但可以碰撞
• 运动刚体:零质量,可以由用户进行动画控制,但只存在单向交互:动态物体会被推开,但不会受到动力学物体的影响
所有这些都需要添加到动力学世界中。刚体可以分配一个碰撞形状。此形状用于计算质量分布,也称为惯性张量。
质心 世界变换
在 Bullet 中,刚体的世界变换始终等于其质心,并且其底层还定义了惯性局部框架。局部惯性张量取决于形状,btCollisionShape 类提供了一种方法来根据给定的质量计算局部惯性。
在物理模拟中,刚体的转动惯量(moment of inertia)是描述物体旋转能力的重要参数。不同的形状有不同的转动惯量公式。对于一个长方体,其绕不同轴的转动惯量分别为:
x = (1/12) * mass * (y² + z²)
y = (1/12) * mass * (x² + z²)
z = (1/12) * mass * (x² + y²)
此世界变换必须是一个刚体变换,这意味着它不应包含缩放、剪切等操作。如果你希望对象发生缩放,可以缩放碰撞形状。其他变换(如剪切)可以通过将它们烘焙到三角形网格的顶点中来应用。
如果碰撞形状与质心变换不对齐,则可以使用 btCompoundShape 并利用子变换来移动子碰撞形状以匹配。
什么是运动状态?
运动状态是 Bullet 为你处理所有繁重工作的一种方式,以便在模拟对象的世界变换传递到程序的渲染部分时,Bullet 可以自动获取该变换。
在大多数情况下,你的游戏循环会在每一帧渲染之前遍历你正在模拟的所有对象。对于每个对象,你会从物理体中更新渲染对象的位置。Bullet 使用称为运动状态的东西来为你节省这一努力。
使用运动状态还有其他多个好处:
• 仅对移动的物体进行与其位置更改相关的计算;如果你有一个静止不动的对象,就没有必要每帧都更新它的位置。
• 你不必只是在其中处理渲染相关的内容。它们可能在网络代码中非常有效,用于通知网络代码某个体已移动并需要在网络上进行更新。
• 插值通常只在屏幕上可见内容的上下文中才有意义。Bullet 通过 MotionStates 管理刚体插值。
• 可以跟踪图形对象和质心变换之间的偏移
• 它们很简单
插值
bullet 知道如何为你插值刚体运动。如前所述,插值的实现是通过 MotionStates 处理的。
如果你尝试通过 btCollisionObject::getWorldTransform 或 btRigidBody::getCenterOfMassTransform 查询一个物体的位置,它会返回在上一次物理时间步结束时的位置。由于 Bullet 每个时间步都会自动插值世界变换以匹配应用程序的时间步,因此你不需要处理插值。
如果需要获取刚体的非插值位置(这将是最后一次物理时间步计算得出的位置),请使用btRigidBody::getWorldTransform()并直接查询该刚体。
那么如何使用它呢?
在Bullet中,运动状态在两个地方被使用。
第一个是在刚体首次创建时。当刚体进入模拟时,Bullet会从运动状态中获取初始位置。Bullet会将一个引用传递给您希望用变换信息填充的变量,并调用getWorldTransform。
Bullet也会对刚性身体动态调用getWorldTransform。请参见下文。
在第一次更新之后,在模拟过程中,Bullet会调用刚体的运动状态以移动该刚体。
Bullet使用setWorldTransform传递刚体的变换信息,要求您相应地更新对象。
要实现一个自定义的运动状态,只需继承btMotionState并重写getWorldTransform和setWorldTransform方法。
默认运动状态
虽然建议从btMotionState接口派生您的运动状态,但Bullet提供了一个默认运动状态供您使用。只需用刚体的初始变换构造它即可:
btDefaultMotionState* ms = new btDefaultMotionState();
在附录中有一个Ogre3D运动状态示例。
动力学物体
如果您计划对静态或移动对象进行动画处理或移动它们,则应将它们标记为动力学物体。此外,在动画期间禁用它们的睡眠/停用状态。这意味着Bullet动力学世界将在每个模拟帧从btMotionState获取新的世界变换信息。
body->setCollisionFlags(body->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT);
body->setActivationState(DISABLE_DEACTIVATION);
如果您使用的是动力学物体,则会在每个模拟步骤中调用.getWorldTransform。这意味着您的动力学物体的运动状态应有一种机制将当前动力学物体的位置推送到运动状态。
仿真帧和插值帧
默认情况下,Bullet物理学模拟在内部以60赫兹(0.01666秒)固定帧率运行。游戏或应用程序可能具有不同的甚至可变的帧率。为了使应用程序帧率与模拟帧率解耦,stepSimulation中内置了自动插值方法:当应用程序 deltaTime 小于内部固定时间步时,Bullet会插值世界变换,并将插值后的世界变换发送到btMotionState,而不执行物理学模拟。如果应用程序的时间步大于60赫兹,则在每次“stepSimulation”调用期间可能会执行多个模拟步骤。用户可以通过传递第二个参数的最大值来限制最大模拟步骤数。
当创建刚体时,它们会从btMotionState中获取初始世界变换信息,使用btMotionState::getWorldTransform方法。在模拟运行时,使用stepSimulation更新活动刚体的新世界变换,并通过btMotionState::setWorldTransform方法传递变换信息。
具有正质量的刚体是动力学刚体,其运动由模拟决定。静态和动力学刚体具有零质量。静态对象不应被用户移动。
第七章 约束
在Bullet中有几种约束被实现。请查看examples/ConstraintDemo以了解每个约束的示例。所有的约束,包括btRaycastVehicle,都从btTypedConstraint派生而来。约束作用于两个刚体之间,其中至少一个刚体必须是动态的。
点到点的约束
点对点约束限制了变化,使得两个刚体的局部枢轴点在世界坐标系中匹配,可以利用此约束将一连串的刚体连接起来。
btPoint2PointConstraint(btRigidBody& rbA,const btVector3& pivotInA);
btPoint2PointConstraint(btRigidBody& rbA,btRigidBody& rbB, const btVector3& pivotInA,const btVector3& pivotInB);
销约束
销约束(或回转关节)限制了两个附加的旋转自由度,因此物体只能围绕一个轴线(销钉轴)旋转。这可以用来表示门或车轮绕单轴旋转的情况。用户可以指定销钉的限制和电机。
btHingeConstraint(btRigidBody& rbA,const btTransform& rbAFrame, bool useReferenceFrameA = false);
btHingeConstraint(btRigidBody& rbA,const btVector3& pivotInA,btVector3& axisInA, bool useReferenceFrameA = false);
btHingeConstraint(btRigidBody& rbA,btRigidBody& rbB, const btVector3& pivotInA,const btVector3&
pivotInB, btVector3& axisInA,btVector3& axisInB, bool useReferenceFrameA = false);
btHingeConstraint(btRigidBody& rbA,btRigidBody& rbB, const btTransform& rbAFrame, const btTransform& rbBFrame, bool useReferenceFrameA = false);
滑动约束
滑动约束使物体能够绕一个轴旋转并沿该轴移动
btSliderConstraint(btRigidBody& rbA, btRigidBody& rbB, const btTransform& frameInA, const btTransform& frameInB ,bool useLinearReferenceFrameA)
锥形扭曲约束
要创建布娃娃,锥形扭曲约束对于像上臂这样的肢体非常有用。这是一个特殊的点到点约束,添加了圆锥和扭曲轴的限制。x轴作为扭曲轴。
btConeTwistConstraint(btRigidBody& rbA,const btTransform& rbAFrame);
btConeTwistConstraint(btRigidBody& rbA,btRigidBody& rbB,const btTransform& rbAFrame, const btTransform& rbBFrame);
通用 6 自由度约束
本通用约束可通过配置每个自由度(dof)来模拟多种标准约束。前三个 dof 轴是线性轴,表示刚体的平移运动,而后三个 dof 轴则表示旋转运动。每一个轴都可以被锁定、保持自由或限制在一定范围内。在创建新的 btGeneric6DofSpring2Constraint 对象时,默认所有轴都被锁定,之后可以根据需要重新配置这些轴。需要注意的是,包括自由和/或有约束的旋转自由度在内的某些组合是未定义的。请参阅 Bullet/examples/Dof6SpringSetup.cpp 以获取更多信息。
btGeneric6DofConstraint(btRigidBody& rbA, btRigidBody& rbB, const btTransform& frameInA, const btTransform& frameInB, bool useLinearReferenceFrameA);
约定如下:
btVector3 lowerSliderLimit = btVector3(-10, 0, 0);
btVector3 hiSliderLimit = btVector3(10, 0, 0);
btGeneric6DofSpring2Constraint* slider = new btGeneric6DofSpring2Constraint(*d6body0, *fixedBody1, frameInA, frameInB);
slider->setLinearLowerLimit(lowerSliderLimit);
slider->setLinearUpperLimit(hiSliderLimit);
对于每个轴:
• 下限 == 上限 -> 轴被锁定。
• 下限 > 上限 -> 轴保持自由。
• 下限 < 上限 -> 轴在该范围内受到限制。
建议使用 btGeneric6DofSpring2Constraint,因为它对原始的 btGeneric6Dof(Spring)Constraint 有一些改进。
第八章 动作:车辆和角色控制器
动作接口
在某些情况下,在物理管道中处理一些自定义的物理游戏代码是有用的。尽管可以使用一个时间步回调函数,但如果需要更新的对象很多,那么更方便的做法是让您的自定义类继承自btActionInterface,并实现btActionInterface::updateAction(btCollisionWorld* world, btScalar deltaTime);方法。Bullet中有内置的例子,如btRaycastVehicle和btKinematicCharacterController,它们使用了这个btActionInterface。
射线追踪车辆
对于街机风格的车辆模拟,建议使用btRaycastVehicle中提供的简化的Bullet车辆模型。与分别将每个车轮和底盘作为刚体并通过约束连接起来的方法不同,它采用了一个简化的模型。这种简化模型具有许多优势,并且在商业驾驶游戏中得到了广泛应用。
整个车辆由一个刚体——底盘来表示。通过射线投掷来近似车轮的碰撞检测,轮胎摩擦力使用了基本的各向异性摩擦模型。有关更多详细信息,请参见src/BulletDynamics/Vehicle和examples/ForkLiftDemo,或访问Bullet论坛。
Kester Maddock在这里分享了一份关于Bullet车辆模拟的有趣文档:http://tinyurl.com/ydfb7lm
角色控制器
玩家或NPC角色可以使用圆柱体、球体或其他形状构建。为了避免旋转,可以将“角度系数”设置为零,这样在碰撞和其他约束中禁用角旋转效果。请参阅btRigidBody::setAngularFactor。其他选项(不太推荐)包括将上轴的逆惯性张量设为零,或使用仅限于角度的铰链约束。
还有一个实验性的btKinematicCharacterController作为示例,非物理字符控制器。它使用btGhostShape执行碰撞查询,创建一个角色可以爬楼梯、光滑地沿墙壁滑动等。请参阅src/BulletDynamics/Character和Demos/CharacterDemo了解其用法。
第九章 软刚体动力学
简介
软刚体动力学在现有的刚体动力学基础上,提供了绳子、布料模拟和体积型软刚体。软刚体与刚体以及碰撞物体之间存在双向交互作用。
• btSoftBody 是主要的软刚体对象,它继承自 btCollisionObject。与刚体不同,软刚体没有单个世界变换:每个节点/顶点均以世界坐标指定。
• btSoftRigidDynamicsWorld 是软刚体、刚体和碰撞物体的容器。
最好通过example/SoftBodyDemo学习如何使用软刚体模拟。
从三角形网格构建
btSoftBodyHelpers::CreateFromTriMesh 可以自动从三角形网格创建软刚体。
碰撞群集
默认情况下,软刚体会在顶点(节点)和面(三角形)之间执行碰撞检测。这需要密集的网格划分,否则可能会遗漏碰撞。改进的方法是使用自动分解为凸形可变形群集。要启用碰撞群集,请使用:
psb->generateClusters(numSubdivisions);
// 启用软刚体与刚体之间的集群碰撞
psb->m_cfg.collisions += btSoftBody::fCollision::CL_RS;
// 启用软刚体与软刚体之间的集群碰撞
psb->m_cfg.collisions += btSoftBody::fCollision::CL_SS;
示例浏览器中的软刚体具有调试选项,可以可视化凸形碰撞群集。
向软刚体施加力
有方法对每个顶点(节点)或单个节点施加力:
softbody ->addForce(const btVector3& forceVector);
softbody ->addForce(const btVector3& forceVector,int node);
软刚体约束
可以将一个或多个顶点(节点)固定,使其不可移动:
softbody->setMass(node,0.f);
或者将软刚体的一个或多个顶点附加到刚体上:
softbody->appendAnchor(int node,btRigidBody* rigidbody, bool
disableCollisionBetweenLinkedBodies=false);
还可以使用约束将两个软刚体连接在一起,参见 Bullet/Demos/SoftBody。
第十章 Bullet示例
2.83版本新增了一个基于OpenGL 3+的示例浏览器,取代了之前的Glut演示。有一个命令行选项支持 OpenGL 2:--opengl2
该示例浏览器在Windows、Linux和Mac OSX上进行了测试。它具备Retina显示支持,在Mac OSX上显示效果最佳。
每个示例也可以单独编译而无需图形。查看 examples/BasicDemo/main.cpp 以了解如何操作。
BSP 演示
导入一个 Quake .bsp 文件并将刷子转换为凸对象。这比使用三角形更高效。
车辆演示
此演示展示了内置车辆的使用。车轮通过射线投射来近似。这种近似方法非常适合快速移动的车辆。
叉车演示
一个展示如何利用约束(如铰链和滑块约束)构建叉车车辆的演示。
第十一章 高级底层技术演示
碰撞接口演示
此演示展示了如何在不使用动力学的情况下使用 Bullet 碰撞检测。它使用 `btCollisionWorld` 类,并填充为 `btCollisionObjects`。调用 `performDiscreteCollisionDetection` 方法,演示如何收集接触点。
碰撞演示
此演示比之前的碰撞接口演示更底层。它直接使用 `btGJKPairDetector` 查询两个对象之间的最近点。
用户定义的碰撞算法
展示了如何注册自定义的碰撞检测算法来处理特定类型的碰撞对。一个简单的球-球案例覆盖了默认 GJK 检测。
Gjk 凸体投射/扫掠演示
此演示展示如何在两个碰撞物体之间执行线性扫掠,并返回冲击时间。这可以用于避免相机和角色控制中的穿透。
连续凸体碰撞
展示了使用连续碰撞检测的时间冲击查询,涉及两个旋转和移动的物体。它使用 Bullet 的保守推进实现。
光线追踪器演示
此演示展示 CCD 光线投射在碰撞形状上的应用。它实现了一个光线追踪器,能够准确可视化碰撞形状的隐式表示。这包括碰撞边距、凸包、米氏owski 求和以及其他难以直观可视化的形状。
单纯形演示
这是一个非常底层的演示,用于测试 GJK 子距离算法的内部工作原理。此演示计算简单形与原点之间的距离,并用红线绘制。一个单纯形包含 1 到 4 个点,演示展示了 4 点的情况,即四面体。使用的解算器是 Voronoi 单纯形求解器,如 Christer Ericson 在其碰撞检测书中所述。
第十二章 作者工具和序列化
在3D建模软件中,可以通过创建碰撞体形状、刚体和约束,并将它们导出为Bullet物理引擎可读取的文件格式。
Blender
开源的3D制作套件Blender使用Bullet物理进行动画和其内置游戏引擎。请访问[http://blender.org](http://blender.org)获取更多信息。
Blender提供了一个选项,可以将COLLADA Physics文件导出到其中。此外,还有一个项目可以直接从Blender的.blend文件中读取所有信息,包括碰撞形状、刚体和约束信息。请访问[http://gamekit.googlecode.com]获取更多信息。
Blender 2.57及以上版本提供了一个直接从游戏引擎导出到.bullet文件的选项。这可以通过PhysicsConstraints模块中的Python命令`exportBulletFile("name.bullet")`实现。
序列化和Bullet .bullet二进制格式
从Bullet 2.76版本开始,具有将动力学世界保存为二进制转储的能力。对象和形状直接存储在一个缓冲区中,因此不需要额外的库。以下是将动力学世界保存到二进制.bullet文件的一个示例:
btDefaultSerializer* serializer = new btDefaultSerializer();
dynamicsWorld->serialize(serializer);
FILE* file = fopen("testFile.bullet","wb");
fwrite(serializer->getBufferPointer(), serializer->getCurrentBufferSize(), 1, file);
fclose(file);
在大多数Bullet示例中,按下F3键即可保存为“testFile.bullet”。要读取.bullet文件,请使用Bullet/examples/Importers/ImportBullet中的`btBulletWorldImporter`实现。
有关.bullet序列化的更多信息,请访问Bullet维基百科上的[http://bulletphysics.org/mediawiki-1.5.8/index.php/Bullet_binary_serialization]
十三 小提示
### 一般提示
避免使用非常小和非常大的碰撞体形状
移动物体的最小尺寸约为0.2单位,相当于地球重力下的20厘米。如果操作更小的物体或更大的重力,则应相应降低内部模拟频率,使用btDiscreteDynamicsWorld::stepSimulation的第三个参数。默认情况下为60Hz。例如,投掷骰子(1厘米宽的方块,重力为9.8m/s²)需要至少300Hz的频率(1/300)。建议将移动物体的最大尺寸保持在约5单位/米以下。
避免大质量比(差异)
当一个重物静止在一个非常轻的物体上时,模拟会变得不稳定。最好将质量控制在1左右。这意味着坦克与非常轻物体之间的准确交互是不现实的。
将多个静态三角形网格合并为一个
许多小btBvhTriangleMeshShape会污染广相探测(broadphase)。最好将它们合并。
使用默认内部固定时间步长
Bullet最适合使用至少60赫兹(1/60秒)的固定内部时间步长。
为了安全和稳定,Bullet会自动将可变时间步长分解为固定的内部模拟子步骤,最多分解为stepSimulation的第二个参数指定的最大子步骤数。当时间步长小于内部子步骤时,Bullet会插值运动。
此安全机制可以通过将最大子步骤数(stepSimulation的第二个参数)设置为0来禁用:内部时间步长和子步骤被禁用,实际时间步长会被模拟。不建议禁用此安全机制。
对于软体模型,请使用btConeTwistConstraint
最好使用bt铰链约束和/或bt锥螺旋限制来构建软体模型,以处理膝盖、肘部和手臂。
不要将碰撞 margin 设置为零
碰撞检测系统需要一定的 margin 以保证性能和稳定性。如果间隙明显,请在图形表示中进行补偿。
在一个凸网中使用少于100个顶点
最好限制btConvexHullShape的顶点数量。这对性能更有利,过多的顶点可能会导致不稳定。可以使用btShapeHull工具来简化凸包。
避免巨大的或退化的三角形在三角形网格中
保持三角形大小合理,例如低于10单位/米。此外,最好避免退化且边长比例差异大或接近零面积的三角形。
分析功能 btQuickProf 会绕过内存分配器
如果需要,可以在检查内存泄漏时禁用分析器,或者在创建软件发行版的最终版本时禁用。可以通过在 Bullet/src/LinearMath/btQuickProf.h 中定义 #define BT_NO_PROFILE 1 来关闭分析功能。