Box2d源碼學習Broad-phase的實現

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

以上文章僅是一家之言,若有不妥、錯誤之處,請大家多多之出。同時也希望能與大家多多交流,共同進步。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章