本系列博客是由扭曲45原创,欢迎转载,转载时注明出处,http://blog.csdn.net/cg0206/article/details/8300658
在一个物理步长内,碰撞处理可以被划分成narrow-phase和broad-phase两个阶段。在narrow-phase阶段计算一对形状的接触。假设有N个形状,直接使用蛮力进行计算,我们需要调用N*N/2次narrow-phase算法。
b2BroadPhase类通过使用动态树降低了管理数据方面的开销。这极大的降低了调用narrow-phase算法的次数。
一般情况下,你不需要直接和broad-phase打交道。Box2D来内部来创建和管理broad-phase。另外,b2BroadPhase是使用Box2D的模拟循环的思路来设计的,所以它可能不适合用于其他用途。
---摘自oh!coder的博客
Box2d中broad-phase用于计算pairs【相交记录】,执行容量查询和光线投射。主要还是调用上一节我们说的动态树进行数据方面的管理。首先,我们还是看看头文件b2BroadPhase.h中的定义部分。
//pair定义 struct b2Pair { int32 proxyIdA; //代理a int32 proxyIdB; //代理b int32 next; //下一个pair }; // broad-phase用于计算pairs,执行体积查询和光线投射 // broad-phase不会持续pairs.相反,它会汇报新的pairs。这取决于客户端是否用掉新的pairs和是否跟踪后续重叠。 class b2BroadPhase { public: //空节点代理 enum { e_nullProxy = -1 }; b2BroadPhase(); ~b2BroadPhase(); /************************************************************************** * 功能描述:创建一个代理,并用aabb初始化。pairs不会汇报直到UpdatePairs被调用 * 参数说明: allocator :soa分配器对象指针 userData :用户数据 * 返 回 值: (void) ***************************************************************************/ int32 CreateProxy(const b2AABB& aabb, void* userData); /************************************************************************** * 功能描述:销毁一个代理,任何pairs的删除都取决于客户端 * 参数说明: proxyId :代理id * 返 回 值: (void) ***************************************************************************/ void DestroyProxy(int32 proxyId); /************************************************************************** * 功能描述:移动一个代理。只要你喜欢可以多次调用MoveProxy, 当你完成后调用UpdatePairs用于完成代理pairs(在你的时间步内) * 参数说明: proxyId :代理id aabb :aabb变量 displacement :移动坐标向量 * 返 回 值: (void) ***************************************************************************/ void MoveProxy(int32 proxyId, const b2AABB& aabb, const b2Vec2& displacement); /************************************************************************** * 功能描述: 在下次调用UpdatePairs时,调用一个触发器触发它的pairs * 参数说明: proxyId :代理id * 返 回 值: (void) ***************************************************************************/ void TouchProxy(int32 proxyId); /************************************************************************** * 功能描述: 获取宽大的aabb * 参数说明: proxyId :代理id * 返 回 值: (void) ***************************************************************************/ const b2AABB& GetFatAABB(int32 proxyId) const; /************************************************************************** * 功能描述: 通过一个代理获取userData,如果id无效,返回NULL * 参数说明: proxyId :代理id * 返 回 值: 用户数据 ***************************************************************************/ void* GetUserData(int32 proxyId) const; /************************************************************************** * 功能描述: 测试宽大aabb的重复部分 * 参数说明: proxyIdA :A代理id proxyIdB :B代理id * 返 回 值: true :不重叠 false:重 叠 ***************************************************************************/ bool TestOverlap(int32 proxyIdA, int32 proxyIdB) const; /************************************************************************** * 功能描述: 获取代理数量 * 参数说明: (void) * 返 回 值: 代理数量 ***************************************************************************/ int32 GetProxyCount() const; /************************************************************************** * 功能描述: 更新pairs.这会对pair进行回调。只能添加pairs * 参数说明: callback :回调对象 * 返 回 值: (void) ***************************************************************************/ template <typename T> void UpdatePairs(T* callback); /************************************************************************** * 功能描述: 在重叠代理中查询一个aabb.每个提供aabb重叠的代理将会被回调类调用 * 参数说明: callback :回调对象类 aabb :aabb变量 * 返 回 值: (void) ***************************************************************************/ template <typename T> void Query(T* callback, const b2AABB& aabb) const; /************************************************************************** * 功能描述: 光线投射在树上的代理。 这依赖于回调被执行一个精确的光线投射在一个代理包含一个形状 * 参数说明: callback : 一个回调对象类,当被调用时,光线将会撒到每个代理中。 input :光线投射输入数据。这个光线从p1扩展到p1+maxFraction *(p2 - p1) * 返 回 值: (void) ***************************************************************************/ template <typename T> void RayCast(T* callback, const b2RayCastInput& input) const; /************************************************************************** * 功能描述: 获取嵌入树的高度 * 参数说明: (void) * 返 回 值: (void) ***************************************************************************/ int32 GetTreeHeight() const; /************************************************************************** * 功能描述: 获取嵌入树的平衡值 * 参数说明: (void) * 返 回 值: (void) ***************************************************************************/ int32 GetTreeBalance() const; /************************************************************************** * 功能描述: 获取嵌入树的质量,即是树的总aabbs周长与根节点aabb周长的比 * 参数说明: (void) * 返 回 值: 树的质量 ***************************************************************************/ float32 GetTreeQuality() const; private: //友元类 friend class b2DynamicTree; /************************************************************************** * 功能描述: 根据代理id添加代理到移动缓冲区中 * 参数说明: proxyId :代理id * 返 回 值: (void) ***************************************************************************/ void BufferMove(int32 proxyId); /************************************************************************** * 功能描述: 将代理移出移动缓存区 * 参数说明: proxyId :代理id * 返 回 值: (void) ***************************************************************************/ void UnBufferMove(int32 proxyId); /************************************************************************** * 功能描述: 查询回调函数 * 参数说明: proxyId :代理id * 返 回 值: true :表示正常回调 ***************************************************************************/ bool QueryCallback(int32 proxyId); //动态树声明 b2DynamicTree m_tree; //代理数量 int32 m_proxyCount; //移动的缓冲区 int32* m_moveBuffer; //移动缓冲区的总容量 int32 m_moveCapacity; //需要移动的代理数量 int32 m_moveCount; //pair缓冲区 b2Pair* m_pairBuffer; //pair缓冲区中的总容量 int32 m_pairCapacity; //pair数量 int32 m_pairCount; //查询代理id int32 m_queryProxyId; };
在这类中,可以看到b2BroadPhase将b2DynamicTree定义为友元类,也就是说b2DynamicTree每一个对象均可访问b2BroadPhase的任何成员,不管是否是私有的。同时我们又可以看到在b2BrodPhase类中,我们定义了个动态树对象m_tree,这样我们形成的好像是形成了一个环,但m_tree并不能访问b2DynamicTree中的私有变量。其他部分,不多说了,看注释。再来看内联函数的实现。
/************************************************************************** * 功能描述: 用于pairs的排序 * 参数说明: pair1:Pari对象引用 pair2: Pari对象引用 * 返 回 值: true : pair1较小 fasle:pair2较小 ***************************************************************************/ inline bool b2PairLessThan(const b2Pair& pair1, const b2Pair& pair2) { //比对pair的代理idA if (pair1.proxyIdA < pair2.proxyIdA) { return true; } //再比对代理idB if (pair1.proxyIdA == pair2.proxyIdA) { return pair1.proxyIdB < pair2.proxyIdB; } return false; } //根据代理id获取userData inline void* b2BroadPhase::GetUserData(int32 proxyId) const { return m_tree.GetUserData(proxyId); } //测试重叠 inline bool b2BroadPhase::TestOverlap(int32 proxyIdA, int32 proxyIdB) const { const b2AABB& aabbA = m_tree.GetFatAABB(proxyIdA); const b2AABB& aabbB = m_tree.GetFatAABB(proxyIdB); return b2TestOverlap(aabbA, aabbB); } //获取aabb inline const b2AABB& b2BroadPhase::GetFatAABB(int32 proxyId) const { return m_tree.GetFatAABB(proxyId); } //获取代理数量 inline int32 b2BroadPhase::GetProxyCount() const { return m_proxyCount; } //获取树的高度 inline int32 b2BroadPhase::GetTreeHeight() const { return m_tree.GetHeight(); } //获取树的平衡值 inline int32 b2BroadPhase::GetTreeBalance() const { return m_tree.GetMaxBalance(); } //获取树的质量 inline float32 b2BroadPhase::GetTreeQuality() const { return m_tree.GetAreaRatio(); } //更新pairs template <typename T> void b2BroadPhase::UpdatePairs(T* callback) { //重置pair缓存区 m_pairCount = 0; //执行查询树上所有需要移动代理 for (int32 i = 0; i < m_moveCount; ++i) { m_queryProxyId = m_moveBuffer[i]; if (m_queryProxyId == e_nullProxy) { continue; } // 我们需要查询树的宽大的AABB,以便当我们创建pair失败时,可以再次创建 const b2AABB& fatAABB = m_tree.GetFatAABB(m_queryProxyId); // 查询树,创建多个pair并将他们添加到pair缓冲区中 m_tree.Query(this, fatAABB); } //重置移动缓冲区 m_moveCount = 0; // 排序pair缓冲区 std::sort(m_pairBuffer, m_pairBuffer + m_pairCount, b2PairLessThan); // 发送pair到客户端 int32 i = 0; while (i < m_pairCount) { //在pair缓冲区中获取当前的pair b2Pair* primaryPair = m_pairBuffer + i; //根据相交记录 void* userDataA = m_tree.GetUserData(primaryPair->proxyIdA); void* userDataB = m_tree.GetUserData(primaryPair->proxyIdB); callback->AddPair(userDataA, userDataB); ++i; //跳过重复的pair while (i < m_pairCount) { b2Pair* pair = m_pairBuffer + i; if (pair->proxyIdA != primaryPair->proxyIdA || pair->proxyIdB != primaryPair->proxyIdB) { break; } ++i; } } // Try to keep the tree balanced. //m_tree.Rebalance(4); } //区域查询 template <typename T> inline void b2BroadPhase::Query(T* callback, const b2AABB& aabb) const { m_tree.Query(callback, aabb); } //光线投射 template <typename T> inline void b2BroadPhase::RayCast(T* callback, const b2RayCastInput& input) const { m_tree.RayCast(callback, input); }
关于这部分,就是对动态树中相关方法的封装,如果还有童鞋有疑问的话,不妨看看我的上一篇文章《Box2d源码学习<六>动态树的实现》,接下来来看看b2BroadPhase部分。
//构造函数,初始化数据 b2BroadPhase::b2BroadPhase() { m_proxyCount = 0; m_pairCapacity = 16; m_pairCount = 0; m_pairBuffer = (b2Pair*)b2Alloc(m_pairCapacity * sizeof(b2Pair)); m_moveCapacity = 16; m_moveCount = 0; m_moveBuffer = (int32*)b2Alloc(m_moveCapacity * sizeof(int32)); } //析构函数 b2BroadPhase::~b2BroadPhase() { b2Free(m_moveBuffer); b2Free(m_pairBuffer); } //创建一个代理 int32 b2BroadPhase::CreateProxy(const b2AABB& aabb, void* userData) { //获取代理id int32 proxyId = m_tree.CreateProxy(aabb, userData); //代理数量自增 ++m_proxyCount; //添加代理到移动缓冲区中 BufferMove(proxyId); return proxyId; } //销毁一个代理 void b2BroadPhase::DestroyProxy(int32 proxyId) { UnBufferMove(proxyId); --m_proxyCount; m_tree.DestroyProxy(proxyId); } //移动一个代理 void b2BroadPhase::MoveProxy(int32 proxyId, const b2AABB& aabb, const b2Vec2& displacement) { bool buffer = m_tree.MoveProxy(proxyId, aabb, displacement); if (buffer) { BufferMove(proxyId); } } //在下次调用UpdatePairs时,调用一个触发器触发它的pairs void b2BroadPhase::TouchProxy(int32 proxyId) { BufferMove(proxyId); } //根据代理id添加代理到移动缓冲区中 void b2BroadPhase::BufferMove(int32 proxyId) { //移动缓冲区过小,增容 if (m_moveCount == m_moveCapacity) { //获取移动缓冲区 int32* oldBuffer = m_moveBuffer; //将容量扩增为原来的2倍 m_moveCapacity *= 2; //重新申请移动缓冲区 m_moveBuffer = (int32*)b2Alloc(m_moveCapacity * sizeof(int32)); //拷贝旧的移动缓冲区内容到新的里面去,并释放旧的移动缓冲区 memcpy(m_moveBuffer, oldBuffer, m_moveCount * sizeof(int32)); b2Free(oldBuffer); } //添加代理id到移动缓冲区中 m_moveBuffer[m_moveCount] = proxyId; //自增 ++m_moveCount; } //移除移动缓存区 void b2BroadPhase::UnBufferMove(int32 proxyId) { //查找相应的代理 for (int32 i = 0; i < m_moveCount; ++i) { //找到代理,并置空 if (m_moveBuffer[i] == proxyId) { m_moveBuffer[i] = e_nullProxy; return; } } } //当我们聚集pairs时这个函数将会被b2DynamicTree:Query调用 bool b2BroadPhase::QueryCallback(int32 proxyId) { // 一个代理不需要自己pair更新自己的pair if (proxyId == m_queryProxyId) { return true; } // 如果需要增加pair缓冲区 if (m_pairCount == m_pairCapacity) { //获取旧的pair缓冲区,并增加容量 b2Pair* oldBuffer = m_pairBuffer; m_pairCapacity *= 2; //重新申请pair缓冲区,并拷贝旧缓冲区中的内容 m_pairBuffer = (b2Pair*)b2Alloc(m_pairCapacity * sizeof(b2Pair)); memcpy(m_pairBuffer, oldBuffer, m_pairCount * sizeof(b2Pair)); //释放旧的pair缓冲区 b2Free(oldBuffer); } //设置最新的pair //并自增pair数量 m_pairBuffer[m_pairCount].proxyIdA = b2Min(proxyId, m_queryProxyId); m_pairBuffer[m_pairCount].proxyIdB = b2Max(proxyId, m_queryProxyId); ++m_pairCount; return true; }
通过源代码我们可以看到,它的实现主要靠移动缓冲区(m_moveBuffer)和pair缓冲区(m_pariBuffer)。构造函数b2BroadPhase()主要是申请这两个缓冲区,析构函数~b2BroadPhase()释放这两个缓冲区,创建一个代理函数CreateProxy()主要添加代理到移动缓冲区,销毁代理函数DestroyProxy主要是销毁一个在移动缓冲区的代理,MoveProxy()、TouchProxy()、BufferMove()均是在移动缓冲区中添加代理,UnBufferMove()是在移动缓冲区中移除代理,QueryCallback()是对pair缓冲区的操作。
突然我们心中有一个疑问,这两个缓冲区各自操作各自的,通过这段代码我们看不到任何的联系,它们到底是如何通信的呢?请先大家思考下。。。
好了,大家知道了吗?有人猜到是通过UpdatePairs函数实现的,这是正确的,但具体的还是通过动态树中的Query函数来实现的,我们不妨回顾一下updatepairs中的代码段。
//执行查询树上所有需要移动代理 for (int32 i = 0; i < m_moveCount; ++i) { m_queryProxyId = m_moveBuffer[i]; if (m_queryProxyId == e_nullProxy) { continue; } // 我们需要查询树的宽大的AABB,以便当我们创建pair失败时,可以再次创建 const b2AABB& fatAABB = m_tree.GetFatAABB(m_queryProxyId); // 查询树,创建多个pair并将他们添加到pair缓冲区中 m_tree.Query(this, fatAABB); }
/************************************************************************** * 功能描述:查询一个aabb重叠代理,每个重叠提供AABB的代理都将回调回调类 * 参数说明:callback :回调对象 aabb :要查询的aabb * 返 回 值:aabb对象 ***************************************************************************/ template <typename T> void Query(T* callback, const b2AABB& aabb) const;再看Query函数中的代码片段
//是否成功 bool proceed = callback->QueryCallback(nodeId); if (proceed == false) { return; }看这句代码 bool proceed = callback->QueryCallback(nodeId); 此处的callback就是刚刚传进去的this,也就是说我们调用的QueryCallback也就是b2BroadPhase::QueryCallback(int32 proxyId)函数。到此处相信大家已经明白了。
ps:
以上文章仅是一家之言,若有不妥、错误之处,请大家多多之出。同时也希望能与大家多多交流,共同进步。