BulletPhysics物理引擎手册(3)

Bullet讨论群:533030320

在前几篇中我们了解了bullet的一个大概的运行流程,当然在具体运行之中我们需要精确调试每一步骤,所以这一篇的内容是:调试渲染。

一切井然有序 — 物体管理和debug渲染

我们到目前为止一直使用hard code来创建一个box,当我们不停的增加物体时也就是场景中有越来越多的物体时我们很难一个个去管理他们了
一个聪明的方法是在物理系统建立之初就打造一个系统去封装重要的物体信息(比如物体的形状,碰撞的形状(不必要和渲染的形状一样,比如人的碰撞包围形可以是个胶囊),位置运动信息motion state,和颜色等),这些重要的信息最好都有独立的class去表示,这样我们才能统一管理和调用,而不是全部塞在物体之中。
这样的做法使我们可以只关注物体的某些信息,在遍历物体并更新渲染的时候可以调用统一的接口。

为了实现这个目的,我们对物体管理系统做了5个重大调整

  • 创建GameObject类来存储我们的物体信息
  • 全部以GameObject来实例化我们的物体并把他们储存在一个数据结构之中
  • 修改我们的渲染系统,去遍历上述的数据结构
  • 渲染系统检测物体的形状shape,并对应渲染多边形polygen
  • 写一个新的函数简化物体的创建

做个小小设计师 — 设计我们的物体类

我们希望现在可以创建一些小物件的形状,比如盒子,球,圆柱,然后我们也不希望重复得创建motion state, rigid body的construction info等。根本上两个物体的数据差别如下

  • 重量
  • 形状
  • 颜色
  • 初始位置和旋转

因为只有上述差别,所以我们可以只用这四个参数来初始化物体,其他都自动生成默认。因此我们的GameObject的新构造函如下

GameObject(btCollisionShape* pShape, float mass, const btVector3 &color, const btVector3 &initialPosition = btVector3(0,0,0), const btQuaternion &initialRotation = btQuaternion());

GameObject是一个新类并储存了重要的自定义信息,例如Bullet的一些组件和它的颜色。我们同样可以提供一些类属性的access接口。大部分的工作在构造时完成了,只要我们提供了质量,颜色,形状,和初始transform(某种形式的position vector 和 一个rotation的四元数组合)

大部分的代码都是straightforward的,除了转动惯量 local inertia。这个值通常在物理模拟中计算角动量(转动惯量,角动量详细解释在计算机物理学-刚体动力学(1),(2))

当然还有一种特殊的情况,就是如果我们定义质量为零,那么我们就要设置转动惯量为(0, 0, 0),这样就可以防止碰撞时的转动。另外Bullet用质量为零去暗示这个物体具有无限质量,因此不会被重力影响,其实一切力都不会影响它,除了我们直接改变它的状态。这种object可以用来作为环境组件比如墙面,地板和其他一些。

质心是牛顿物理一个重要属性,Bullet会假定刚体的位置为其质心,除非被另外设定。设定质心到其他位置可以用来表示多个shapes的组合等表示。

来!渲染我们的objects

我们需要一个更统一的宇宙来渲染我们的objects。

事实上Bullet的demo给我们提供了一个标准的接口

void OpenGLGuiHelper::autogenerateGraphicsObjects(btDiscreteDynamicsWorld* rbWorld) 

其中OpenGLGuiHelper继承自标准接口
///The Bullet 2 GraphicsPhysicsBridge let’s the graphics engine create graphics representation and synchronize
struct GUIHelperInterface

virtual void autogenerateGraphicsObjects(btDiscreteDynamicsWorld* rbWorld) =0;

bullet提供了一个简单的点云构造shell算法,和一些标准自动构造物体的类,详情可见
OpenGLGuiHelper::autogenerateGraphicsObjects,当然在这之后笔者会另外开一篇关于autogenerateGraphicsObjects的博文,专门讲述如何从点云生成可渲染的shell,这个内容属于三维模型算法,故不在此篇细论。

我喊你一声SB,你敢不敢答应。 宝葫芦就是一个容器 — 储存我们的objects

选择一个正确的数据结构决定了我们如何处理。任务需要我们walk through所有objects,包括更新/渲染所有物体。同时,我们也没有什么随机存取的功能需求,也没有什么插入删除操作。所以我们用array去储存所有objects。

notes: 在软件界,有句话叫 事先优化是所有罪恶根源。所以不要花太多时间去想到底用什么样的数据结构。没有一个完美的数据结构,一个明智的决定是先让你的程序运行起来,然后再回头优化,这时候你会发现选择什么样的数据结构并不重要,因为有更大的问题等着你。当然事先有个架构的草图是非常必要的,但没必要精细到细节

C++的STL就有一个std::vector类。通常这就够用了。

typedef std::vector<GameObject*> GameObjects;
GameObjects m_objects;
void BulletOpenGLApplication::RenderScene() {
    // create an array of 16 floats (representing a 4x4 matrix)
    btScalar transform[16];
    // iterate through all of the objects in our world
    for(GameObjects::iterator i = m_objects.begin(); i !=
    m_objects.end(); ++i) {
    // get the object from the iterator
    GameObject* pObj = *i;
    // read the transform
    pObj->GetTransform(transform);
    // get data from the object and draw it
    DrawShape(transform, pObj->GetShape(), pObj->GetColor());
    }
}

我们把所有东西放在这个结构中,我们就可以不费吹灰之力添加新的objects去渲染。

哪吒自语:
在接触bullet之初,我就意识到bullet使用了 intel的单指令多数据(SIMD)技术,虽然不如使用GPU加速,但是这种方法在intel为cpu核心的设备上是通用的。但为了使用这种技术我们的数据必须是16位对齐的,但是STL不满足这种需求。但Bullet实现了自己的类STL::Vector类 btAlignedObjectArray可以使用SIMD。

创建objects

我们使用上述规则创建物体,并加入array之中管理。并用openGL渲染出来

Debug rendering

可视化是我们分析问题的第一步,所以我们要让渲染系统绘出响应的debug信息,比如物体的包围盒边界,碰撞点,穿透深度和一些其他信息。Bullet提供了一些非常简单的接口,让我们调试。

打造一个debug 绘制器

为了打造一个属于我们的debug 绘制器,我们将要继承实现btIDebugDraw接口,一些重要的方法必须被实现,比如drawLine() or drawContactPoint()。还有一些接口我们暂时用不上reportErrorWarning() and draw3dText(),但是这些方法为纯虚函数的定义,所以我们必须也要最起码空实现他们。

这里有一个代码片snippet 是DebugDrawer中的重要的一个,就是两点画直线

void DebugDrawer::drawLine(const btVector3 &from,const btVector3 &to, const btVector3 &color) { // draws a simple line of pixels between points. // use the GL_LINES primitive to draw lines glBegin(GL_LINES);
    glColor3f(color.getX(), color.getY(), color.getZ());
    glVertex3f(from.getX(), from.getY(), from.getZ());
    glVertex3f(to.getX(), to.getY(), to.getZ());
    glEnd();
}

DebugDrawer类实例必须被交给world object通过setDebugDrawer()方法,事实上它就像一个代理,world object提供数据,DebugDrawer提供实现方法。然后在world object的另外一个函数debugDrawWorld()中调用它进行绘制,其绘制具体内容又m_debugFlags和实现方法决定。所有实现均继承自btIDebugDraw这个接口类,充分使用了C++的多态(polymorphism)
我们可以使用一个按键响应修改debugFlag就像一个开关。所以我们有下实现

case 'a':
    // toggle AABB wireframe debug drawing
    m_pDebugDrawer->ToggleDebugFlag(btIDebugDraw::DBG_DrawAABBs);
    break;

BulletPhysics物理引擎手册(3)_第1张图片

当然还有另外一些调试,鉴于现在已经是凌晨还要笔者还要准备下周出国旅游的攻略地图等,就下回再说。敬请期待

你可能感兴趣的:(物理引擎)