Bullet 学习笔记之 Bullet User Manual - 3 Library Overview
Bullet User Manual 第三部分,参见 bullet3/docs/Bullet_User_Manual.pdf
总体上绍了 Bullet 物理引擎的整体架构、仿真流程、基础数据结构等。
Bullet 物理引擎的主要功能为 碰撞检测、碰撞/约束求解、物体状态的更新。
The main task of a physics engine is to perform collision detection, resolve collisions and other constraints, and provide the updated world transform1 for all the objects.
Bullet 物理引擎的主要组件及其组织结构如下图所示:
Rigid Body Physics Pipeline
(这一部分非常重要,可以帮助理解仿真计算流程,也便于后续基于 Bullet 物理引擎进行软体机器人仿真编程)
Pipeline 如下图所示:
计算流程
在 btDiscreteDynamicsWorld
中,所有的 Pipeline 步骤均包含在函数 stepSimulation
中。结合 btDiscreteDynamicsWorld::stepSimulation(t)
中的代码,各个仿真流程大致对应的代码有:
saveKinematicState(fixedTimeStep * clampedSimulationSteps);
applyGravity();
for (int i = 0; i < clampedSimulationSteps; i++)
{
internalSingleStepSimulation(fixedTimeStep);
synchronizeMotionStates();
}
clearForces();
其中,各个步骤分别为:
-
save Kinematic State
代码为:saveKinematicState(fixedTimeStep * clampedSimulationSteps);
这部分代码,其核心部分就是遍历dynamicWorld
中的非静态、且非睡眠的刚体,执行body->saveKinematicState(timeStep);
-
Apply Gravity
代码为:applyGravity();
具体来说,就是遍历dynamicWorld
中的非静态刚体,执行body->applyGravity();
-
internal Single Step Simulation
代码为:internalSingleStepSimulation(fixedTimeStep);
其中的具体内容非常丰富,有:
BT_PROFILE("internalSingleStepSimulation");
if (0 != m_internalPreTickCallback)
{
(*m_internalPreTickCallback)(this, timeStep);
}
///apply gravity, predict motion
predictUnconstraintMotion(timeStep);
btDispatcherInfo& dispatchInfo = getDispatchInfo();
dispatchInfo.m_timeStep = timeStep;
dispatchInfo.m_stepCount = 0;
dispatchInfo.m_debugDraw = getDebugDrawer();
createPredictiveContacts(timeStep);
///perform collision detection
performDiscreteCollisionDetection();
calculateSimulationIslands();
getSolverInfo().m_timeStep = timeStep;
///solve contact and other joint constraints
solveConstraints(getSolverInfo());
///CallbackTriggers();
///integrate transforms
integrateTransforms(timeStep);
///update vehicle simulation
updateActions(timeStep);
updateActivationState(timeStep);
if (0 != m_internalTickCallback)
{
(*m_internalTickCallback)(this, timeStep);
}
以上内容可以说是几乎囊括了 Pipeline 的所有内容。这个会在后面详细的再分析。
-
synchronize Motion States
代码为:synchronizeMotionStates();
这部分代码,其核心内容,是遍历dynamic World
中的非静态、非Kinematic的刚体/碰撞对象,执行body->getMotionState()->setWorldTransform(interpolatedTransform);
看起来是更新世界坐标系变化对刚体的影响。 -
clearForces();
代码为:clearForces();
这部分代码,就是body->clearForces();
结合 Pipeline 的流程图,各部分对应的代码应该是:
- Apply Gravity
body->saveKinematicState(timeStep);
body->applyGravity();
- Predict Tranforms
///apply gravity, predict motion
predictUnconstraintMotion(timeStep);
即
//don't integrate/update velocities here, it happens in the constraint solver
body->applyDamping(timeStep);
body->predictIntegratedTransform(timeStep, body->getInterpolationWorldTransform());
- Cpmpute AABBs
btCollisionShape::calculateTemporalAabb(...);
可能是在这部分代码中实现的,很小的一部分代码,有点被隐藏起来了。
- Detect Paris
应该是在这部分代码中,即btDiscreteDynamicsWorld::createPredictiveContacts(timeStep);
具体来说,为:
releasePredictiveContacts();
createPredictiveContactsInternal(&m_nonStaticRigidBodies[0], m_nonStaticRigidBodies.size(), timeStep);
包括了清空 m_predictiveManifolds
for (int i = 0; i < m_predictiveManifolds.size(); i++)
{
btPersistentManifold* manifold = m_predictiveManifolds[i];
this->m_dispatcher1->releaseManifold(manifold);
}
m_predictiveManifolds.clear();
以及重新计算可能的 manifold
void btDiscreteDynamicsWorld::createPredictiveContactsInternal(btRigidBody** bodies, int numBodies, btScalar timeStep)
{
btTransform predictedTrans;
for (int i = 0; i < numBodies; i++)
{
btRigidBody* body = bodies[i];
body->setHitFraction(1.f);
if (body->isActive() && (!body->isStaticOrKinematicObject()))
{
body->predictIntegratedTransform(timeStep, predictedTrans);
btScalar squareMotion = (predictedTrans.getOrigin() - body->getWorldTransform().getOrigin()).length2();
if (getDispatchInfo().m_useContinuous && body->getCcdSquareMotionThreshold() && body->getCcdSquareMotionThreshold() < squareMotion)
{
BT_PROFILE("predictive convexSweepTest");
if (body->getCollisionShape()->isConvex())
{
gNumClampedCcdMotions++;
#ifdef PREDICTIVE_CONTACT_USE_STATIC_ONLY
class StaticOnlyCallback : public btClosestNotMeConvexResultCallback
{
public:
StaticOnlyCallback(btCollisionObject* me, const btVector3& fromA, const btVector3& toA, btOverlappingPairCache* pairCache, btDispatcher* dispatcher) : btClosestNotMeConvexResultCallback(me, fromA, toA, pairCache, dispatcher)
{
}
virtual bool needsCollision(btBroadphaseProxy* proxy0) const
{
btCollisionObject* otherObj = (btCollisionObject*)proxy0->m_clientObject;
if (!otherObj->isStaticOrKinematicObject())
return false;
return btClosestNotMeConvexResultCallback::needsCollision(proxy0);
}
};
StaticOnlyCallback sweepResults(body, body->getWorldTransform().getOrigin(), predictedTrans.getOrigin(), getBroadphase()->getOverlappingPairCache(), getDispatcher());
#else
btClosestNotMeConvexResultCallback sweepResults(body, body->getWorldTransform().getOrigin(), predictedTrans.getOrigin(), getBroadphase()->getOverlappingPairCache(), getDispatcher());
#endif
//btConvexShape* convexShape = static_cast<btConvexShape*>(body->getCollisionShape());
btSphereShape tmpSphere(body->getCcdSweptSphereRadius()); //btConvexShape* convexShape = static_cast<btConvexShape*>(body->getCollisionShape());
sweepResults.m_allowedPenetration = getDispatchInfo().m_allowedCcdPenetration;
sweepResults.m_collisionFilterGroup = body->getBroadphaseProxy()->m_collisionFilterGroup;
sweepResults.m_collisionFilterMask = body->getBroadphaseProxy()->m_collisionFilterMask;
btTransform modifiedPredictedTrans = predictedTrans;
modifiedPredictedTrans.setBasis(body->getWorldTransform().getBasis());
convexSweepTest(&tmpSphere, body->getWorldTransform(), modifiedPredictedTrans, sweepResults);
if (sweepResults.hasHit() && (sweepResults.m_closestHitFraction < 1.f))
{
btVector3 distVec = (predictedTrans.getOrigin() - body->getWorldTransform().getOrigin()) * sweepResults.m_closestHitFraction;
btScalar distance = distVec.dot(-sweepResults.m_hitNormalWorld);
btPersistentManifold* manifold = m_dispatcher1->getNewManifold(body, sweepResults.m_hitCollisionObject);
btMutexLock(&m_predictiveManifoldsMutex);
m_predictiveManifolds.push_back(manifold);
btMutexUnlock(&m_predictiveManifoldsMutex);
btVector3 worldPointB = body->getWorldTransform().getOrigin() + distVec;
btVector3 localPointB = sweepResults.m_hitCollisionObject->getWorldTransform().inverse() * worldPointB;
btManifoldPoint newPoint(btVector3(0, 0, 0), localPointB, sweepResults.m_hitNormalWorld, distance);
bool isPredictive = true;
int index = manifold->addManifoldPoint(newPoint, isPredictive);
btManifoldPoint& pt = manifold->getContactPoint(index);
pt.m_combinedRestitution = 0;
pt.m_combinedFriction = gCalculateCombinedFrictionCallback(body, sweepResults.m_hitCollisionObject);
pt.m_positionWorldOnA = body->getWorldTransform().getOrigin();
pt.m_positionWorldOnB = worldPointB;
}
}
}
}
}
}
这部分代码,非常的,xx
- Compute Contacts
BT_PROFILE("performDiscreteCollisionDetection");
btDispatcherInfo& dispatchInfo = getDispatchInfo();
updateAabbs();
computeOverlappingPairs();
btDispatcher* dispatcher = getDispatcher();
{
BT_PROFILE("dispatchAllCollisionPairs");
if (dispatcher)
dispatcher->dispatchAllCollisionPairs(m_broadphasePairCache->getOverlappingPairCache(), dispatchInfo, m_dispatcher1);
}
- Solve constraints
calculateSimulationIslands();
getSolverInfo().m_timeStep = timeStep;
///solve contact and other joint constraints
solveConstraints(getSolverInfo());
- Integrate Position
integrateTransforms(timeStep);
///update vehicle simulation
updateActions(timeStep);
目前来说,大致清楚了 Bullet 物理引擎的工作流程。另外一项需要搞清楚的,是 Bullet 内的数据结构。比如,RigidBody 是如何存储刚体的,表面网格在哪里;得到的碰撞信息是怎么样存储的;约束求解过程具体是什么样子的。
之后的随笔会进一步解释这些内容。