Bullet 学习笔记之 Bullet User Manual

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 是如何存储刚体的,表面网格在哪里;得到的碰撞信息是怎么样存储的;约束求解过程具体是什么样子的。

之后的随笔会进一步解释这些内容。

原文地址:https://www.cnblogs.com/wghou09/p/12813968.html