现在的位置: 首页 > 综合 > 正文

Box2d源码学习<七>Broad-phase的实现

2013年04月20日 ⁄ 综合 ⁄ 共 11775字 ⁄ 字号 评论关闭

本系列博客是由扭曲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:

以上文章仅是一家之言,若有不妥、错误之处,请大家多多之出。同时也希望能与大家多多交流,共同进步。

抱歉!评论已关闭.