这段时间打算将物理引擎整合进来,于是对Bullet这款开源的物理引擎进行了一些研究。Bullet的研究也有了一段时间了,Bullet这个引擎其实很久之前就接触了,只是一直以来,只是跑跑它的例子,也没有研究例子以及源代码,想要整合进入渲染引擎中也就井中月水中花了。今年3月,我曾经制作了一个整合Bullet最简单的一个例子。即模拟物体坠落的,并且写了博客《QtQuick + OpenGL + Bullet初次测试》。这个例子开了一个好头。这段时间开始研究Bullet的一些其它有趣儿的特性了。
Bullet的约束(也称关节),是一个非常有意思的部分,因为它表示了两个碰撞物体之间的物理关系。比如说如果两个珠子是由一根绳子串起来的,那么绳子就代表了两个珠子之间的约束,当然你可以将绳子换成一根橡皮筋或者一根弹簧,它们都代表了不同的约束。这些约束,都有它们的名称,我最近在研究的就是六自由度弹性约束。
研究一个引擎最好的方法就是研究它的例子。对于Bullet也是如此。在Bullet自带的ConstraintDemo中,我看到了由一些非常有意思的约束组织而成的场景。因此从这个地方入手还不错。例子的截图如下:
你可以使用鼠标右键发射方块,来测试一下约束对于这些物体有什么作用。
这个场景包含了多种约束,每一种约束的处理方法都包含了相应的力学公式以及它的加成。我这回只对六自由度弹性约束进行研究,因此将其单独拉了出来。
六自由度弹性约束是一种约束,顾名思义,它可以作六个维度的旋转,并且在平移方面可以保持一定的弹性。上图中的右上角中黄色和蓝色方块形成的就是一个六自由度弹性约束。这个场景将Bullet的约束都显示出来了,让大家都可以了解,约束究竟能够怎样地影响物体。
六自由度弹性约束它具有一下的属性,它们分别如下:
英文名 |
中文名 |
RigidBodyA |
此约束作用的刚体A |
RigidBodyB |
此约束作用的刚体B |
frameInA |
从刚体A到此约束的变换 |
frameInB |
从刚体B到此约束的变换 |
LinearUpperLimit |
平移(线性)最高的限制 |
LinearLowerLimit |
平移(线性)最低的限制 |
AngularUpperLimit |
旋转(角)最高的限制 |
AngularLowerLimit |
旋转(角)最低的限制 |
Spring |
弹簧效果 |
Stiffness |
弹簧的刚性(劲度) |
Damping |
弹簧的衰减 |
我们将默认的例子效果修改一下,设计成我们想要的带有弹簧的效果:
frameInA.setOrigin(btVector3(btScalar(10), btScalar(0), btScalar(0.))); frameInB.setOrigin(btVector3(btScalar(0), btScalar(0), btScalar(0.))); btGeneric6DofSpringConstraint* pGen6DOFSpring = new btGeneric6DofSpringConstraint(*pBodyA, *pBodyB, frameInA, frameInB, true); pGen6DOFSpring->setLinearUpperLimit(btVector3(0, 0, 0)); pGen6DOFSpring->setLinearLowerLimit(btVector3(0, 0, 0)); pGen6DOFSpring->setAngularLowerLimit(btVector3(-0.17f, -0.17f, -0.17f) ); pGen6DOFSpring->setAngularUpperLimit(btVector3(0.17f, 0.17f, 0.17f) ); pGen6DOFSpring->enableSpring(3, true); pGen6DOFSpring->setStiffness(3, 100.0f); pGen6DOFSpring->setDamping(3, 0.25f); pGen6DOFSpring->enableSpring(4, true); pGen6DOFSpring->setStiffness(4, 100.0f); pGen6DOFSpring->setDamping(4, 0.25f); pGen6DOFSpring->enableSpring(5, true); pGen6DOFSpring->setStiffness(5, 100.0f); pGen6DOFSpring->setDamping(5, 0.25f); pBodyA->addConstraintRef( pGen6DOFSpring ); pBodyB->addConstraintRef( pGen6DOFSpring );
将这些代码替换掉ConstraintDemo.cpp的559到576行,然后重新查看效果。发现约束改变了,如下图:
黄色的方块变成一个在六个自由度中都只有-10°到10°的约束。尝试用方块碰撞它,发现会绕着约束进行旋转。
接下来就是模仿的过程了,话说我要亲自尝试一下在Qt Quick + OpenGL下跑Bullet中约束的例子,因此在前期做了很多的工作,初始化相关的类就花费了很多的精力,由于代码篇幅较长,这里只贴出重要的部分:
void initialize( void ) { qDebug( "--- initialize compoundCube physics ---" ); btTransform centerOfMassTransform( btTransform::getIdentity( ) ); centerOfMassTransform.setOrigin( btVector3( 0.2, 0.0, 0.2 ) ); btTransform tr; tr.setIdentity(); tr.setOrigin(btVector3(btScalar(-20.), btScalar(16.), btScalar(0.))); tr.getBasis().setEulerZYX(0,0,0); btRigidBody* pBodyA = createRigidBody( Cube, btVector3( 1.0, 1.0, 1.0 ), 0.0, tr ); pBodyA->setActivationState(DISABLE_DEACTIVATION); tr.setIdentity(); tr.setOrigin(btVector3(btScalar(-10.), btScalar(16.), btScalar(0.))); tr.getBasis().setEulerZYX(0,0,0); btRigidBody* pBodyB = createRigidBody( Cube, btVector3( 1.0, 1.0, 1.0 ), 1.0, tr, centerOfMassTransform ); pBodyB->setActivationState(DISABLE_DEACTIVATION); btTransform frameInA, frameInB; frameInA = btTransform::getIdentity(); frameInB = btTransform::getIdentity(); frameInA.setOrigin(btVector3(btScalar(10), btScalar(0), btScalar(0.))); frameInB.setOrigin(btVector3(btScalar(0), btScalar(0), btScalar(0.))); btGeneric6DofSpringConstraint* pGen6DOFSpring = new btGeneric6DofSpringConstraint( *pBodyA, *pBodyB, frameInA, frameInB, false ); pGen6DOFSpring->setLinearUpperLimit(btVector3(0, 0, 0)); pGen6DOFSpring->setLinearLowerLimit(btVector3(0, 0, 0)); pGen6DOFSpring->setAngularLowerLimit(btVector3(-0.17f, -0.17f, -0.17f) ); pGen6DOFSpring->setAngularUpperLimit(btVector3(0.17f, 0.17f, 0.17f) ); pGen6DOFSpring->enableSpring(3, true); pGen6DOFSpring->setStiffness(3, 100.0f); pGen6DOFSpring->setDamping(3, 0.25f); pGen6DOFSpring->enableSpring(4, true); pGen6DOFSpring->setStiffness(4, 100.0f); pGen6DOFSpring->setDamping(4, 0.25f); pGen6DOFSpring->enableSpring(5, true); pGen6DOFSpring->setStiffness(5, 100.0f); pGen6DOFSpring->setDamping(5, 0.25f); pBodyA->addConstraintRef( pGen6DOFSpring ); pBodyB->addConstraintRef( pGen6DOFSpring ); // End 我们新添加的 m_scene->physics( )->world( ).addConstraint(pGen6DOFSpring, true); pGen6DOFSpring->setDbgDrawSize(btScalar(5.f)); m_constraint = pGen6DOFSpring; // 新添加一个自由落体的 btTransform newTrans; newTrans.setIdentity( ); newTrans.setOrigin( btVector3( -12.0, 60, 0.9 ) ); // btQuaternion q; // q.setEuler( 0.7, 0.6, 0.7 ); // newTrans.setRotation( q ); btRigidBody* newBody = createRigidBody( Cube, btVector3( 2.0, 6.0, 3.0 ), 300.0, newTrans ); newBody->setActivationState( DISABLE_DEACTIVATION ); } void simulate( void ) { //m_constraint->setEquilibriumPoint( ); //qDebug( "--- simulate compoundCube physics ---" ); } btRigidBody* createRigidBody( ShapeType shapeType, const btVector3& half, btScalar mass, const btTransform& startTransform, const btTransform& centerOfMassTransform = btTransform::getIdentity( ) ) { btCollisionShape* shape = Q_NULLPTR; // 创建形状 switch ( shapeType ) { case Sphere:// 球状 shape = new btSphereShape( half.x( ) ); break; case Cube:// 立方体 shape = new btBoxShape( half ); break; case Capsule:// 胶囊 shape = new btCapsuleShape( half.x( ), half.y( ) ); break; default: qDebug( "This piece is code will never be reached unless bug." ); break; } btMotionState* motionState = new btDefaultMotionState( startTransform, centerOfMassTransform ); btVector3 inertia( 0, 0, 0 ); shape->calculateLocalInertia( mass, inertia ); btRigidBody* body = new btRigidBody( mass, motionState, shape, inertia ); m_scene->physics( )->world( ).addRigidBody( body ); return body; }
这里创建的是一个简单的场景:只有一个六自由度弹性约束,两个刚体,还有一个质量非常大的物体,通过点击按钮来启用物理引擎,这样物体会通过做自由落体运动来将重力势能转化为动能,冲击这个刚体。由于设置了旋转弹性因子,因此处于约束另一方的刚体很自然地通过旋转避开了质量大的物体,最终该物体被弹出……
翻开源码,究竟弹性是怎么实现的呢?原来这用到了一个经典的公式:胡克定律。胡克定律的公式是F= k·x,其中k是物体的劲度系数,这里对应的也就是stiffness。看来要了解Bullet这个库的原理,还要我们复习很多物理知识呢。