本系列博客是由扭曲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);
}
有人也许会说,这段代码我们只看到移动缓冲区(m_moveBuffer),看不出来与 pair缓冲区(m_pariBuffer)有任何关系,它们怎么产生联系的呢?注意m_tree.Query(this,fatAABB)这句代码和它传递的参数,this表示当前类的对象,fatAABB表示移动缓冲区中一个代理的AABB,存储了代理的信息。我们再回顾一下query函数:
/**************************************************************************
* 功能描述:查询一个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:
以上文章仅是一家之言,若有不妥、错误之处,请大家多多之出。同时也希望能与大家多多交流,共同进步。