Box2d源碼學習b2Collision之碰撞(上)公共部分的實現

本系列博客是由扭曲45原創,歡迎轉載,轉載時註明出處,http://blog.csdn.net/cg0206/article/details/8390560

Box2d中將碰撞部分單獨放到幾個文件中去實現的,它的結構和函數用於計算接觸點,距離查詢和TOI查詢。我們根據這些文件實現功能的不同,我們將該部分分成兩個小的部分。它們是:

1、  公共部分的實現

2、  具體形狀間的碰撞的實現

 

我們今天就來看第一部分。關於公共部分,主要包括相關結構體的定義和公共函數的實現。

對於b2Collision.h文件,我們也將它分成以下幾個部分:

a)、外部類和常量在頭文件的聲明

b)、結構體的定義

c)、全局函數的聲明

d)、內聯函數的實現

好了,我們就依次來看看相關源碼。

1、外部類和常量在頭文件的聲明

//聲明類
class b2Shape;
class b2CircleShape;
class b2EdgeShape;
class b2PolygonShape;
//定義特徵的無效值
const uint8 b2_nullFeature = UCHAR_MAX;

不多說了,看註釋。

2、  結構體的定義

//特徵,交叉形成的接觸點
// 必須是4字節或者更少
struct b2ContactFeature
{
	enum Type
	{
		e_vertex = 0,
		e_face = 1
	};

	uint8 indexA;		// shapeA的特徵索引
	uint8 indexB;		// shapeB的特徵索引
	uint8 typeA;		// shapeA的特徵類型
	uint8 typeB;		// shapeB的特徵類型
};

//接觸id
union b2ContactID
{
	b2ContactFeature cf;        //特徵對象變量
	uint32 key;					//特徵id,用於快速比較
};
//流形點屬於接觸流形的一個接觸點。它具有的細節涉及到接觸點的幾何學和力學
// 局部點的求解依賴於流形的類型:
// e_circles:circleB的局部中心
// e_faceA  :circleB的局部中心 或者polygonB的夾點
// e_faceB  :polygonA的夾點
// 這個結構存儲在時間步內,所以我們保持它小一些。
// 注意:這個衝量用來作爲內部緩衝,很可能無法提供可靠的接觸的力,尤其在高速碰撞的時候
struct b2ManifoldPoint
{
	b2Vec2 localPoint;		//局部點,求解依賴於流形類型
	float32 normalImpulse;	//法向衝量,用於防止形狀的穿透
	float32 tangentImpulse;	//切向衝量,用於模擬摩擦
	b2ContactID id;			//唯一地標識一個在兩個形狀之間的接觸點
};
// 流形(注:也有人譯爲‘取樣’,在物理和數學中均使用‘流形’,參照http://zh.wikipedia.org/wiki/流形 )
// 流形是兩個凸形狀的接觸部分。
// Box2D支持多種類型的接觸:
// 夾點與平面半徑
// 點與點半徑(圓)
// 局部的點求解取決於流形的類型:
// e_circles:circleA的中心
// e_faceA  : faceA的中心
// e_faceB  :faceB的重心
// 同樣局部法向量的求解:
// e_circles:不用
// e_faceA  : faceA的法向量
// e_faceB  :faceB的法向量
// 我們用這種方式存儲聯繫,以便移動時位置被更正。
//所有接觸場景必須表述爲這些類型中的一個。
//這個結構存儲在時間步內,所以我們保持它小一些。
struct b2Manifold
{
	//流形的類型
	enum Type
	{
		e_circles,                                  //圓
		e_faceA,                                    //面A
		e_faceB                                     //面B
	};

	b2ManifoldPoint points[b2_maxManifoldPoints];	// 接觸點數組
	b2Vec2 localNormal;								// 局部法向量,對Type::e_points沒用
	b2Vec2 localPoint;								// 求解依賴流形類型
	Type type;                                      // 類型
	int32 pointCount;								// 流形的點的總數
};
//  這是用於求解當前狀態下的接觸流形
struct b2WorldManifold
{
	/**************************************************************************
	* 功能描述:根據流形和提供的變換初始化此結構體。假設適度移動從原始狀態開始的。
	            這不能改變點的數量、衝量等等。半徑必須來着與產生流形的形狀。
	* 參數說明: manifold:流形的指針,用於初始化結構體
	             xfA     :變換A的引用
				 radiusA :形狀A的半徑
				 xfB     :變化B的引用
				 radiusB :形狀B的半徑
	* 返 回 值: (void)
	***************************************************************************/
	void Initialize(const b2Manifold* manifold,
					const b2Transform& xfA, float32 radiusA,
					const b2Transform& xfB, float32 radiusB);

	b2Vec2 normal;							//世界向量方向從A到B
	b2Vec2 points[b2_maxManifoldPoints];	//世界接觸點(交點)
};
//接觸點的狀態
enum b2PointState
{
	b2_nullState,		//點不存在
	b2_addState,		//在update中添加點
	b2_persistState,	//點在update中持續存在
	b2_removeState		//點移除update
};

//裁剪頂點結構體,用於接觸流形的求解
struct b2ClipVertex
{
	b2Vec2 v;        //接觸點
	b2ContactID id;  //接觸id
};

//光線輸入數據。光線從p1擴展到到p1 + maxFraction * (p2 - p1)
struct b2RayCastInput
{
	b2Vec2 p1, p2;             //光線(或射線)上的兩個點,其中p1是起始點
	float32 maxFraction;       //需要檢測的光線範圍
};

//光線輸出數據。光線達到p1 + fraction * (p2 - p1),其中p1和 p2來自b2RayCastInput
struct b2RayCastOutput
{
	b2Vec2 normal;            //法向量
	float32 fraction;         //碰撞點位置的參數值
};
///軸對齊包圍盒
struct b2AABB
{
	/**************************************************************************
	* 功能描述:驗證邊界排序是否有效
	* 參數說明: (void)
	* 返 回 值: (void)
	***************************************************************************/
	bool IsValid() const;
	/**************************************************************************
	* 功能描述:獲取AABB的中心點
	* 參數說明: (void)
	* 返 回 值: 中心點座標
	***************************************************************************/
	b2Vec2 GetCenter() const
	{
		return 0.5f * (lowerBound + upperBound);
	}
	/**************************************************************************
	* 功能描述:獲取AABB的區段(寬高的一半)
	* 參數說明: (void)
	* 返 回 值: aabb的區段
	***************************************************************************/
	b2Vec2 GetExtents() const
	{
		return 0.5f * (upperBound - lowerBound);
	}
	/**************************************************************************
	* 功能描述:獲取AABB的周長
	* 參數說明: (void)
	* 返 回 值: AABB的周長
	***************************************************************************/
	float32 GetPerimeter() const
	{
		float32 wx = upperBound.x - lowerBound.x;
		float32 wy = upperBound.y - lowerBound.y;
		return 2.0f * (wx + wy);
	}
	/**************************************************************************
	* 功能描述:合併AABB
	* 參數說明: aabb:aabb的引用
	* 返 回 值: (void)
	***************************************************************************/
	void Combine(const b2AABB& aabb)
	{
		lowerBound = b2Min(lowerBound, aabb.lowerBound);
		upperBound = b2Max(upperBound, aabb.upperBound);
	}
	/**************************************************************************
	* 功能描述:合併兩個AABB,爲對象的aabb賦值
	* 參數說明: aabb1:一個AABB的引用
	             aabb2:一個AABB的引用
	* 返 回 值: (void)
	***************************************************************************/
	void Combine(const b2AABB& aabb1, const b2AABB& aabb2)
	{
		lowerBound = b2Min(aabb1.lowerBound, aabb2.lowerBound);
		upperBound = b2Max(aabb1.upperBound, aabb2.upperBound);
	}
	/**************************************************************************
	* 功能描述:當前aabb是否包含提供的AABB
	* 參數說明: aabb1:提供的AABB的引用
	* 返 回 值: true :包含
	             false:不包含
	***************************************************************************/
	bool Contains(const b2AABB& aabb) const
	{
		bool result = true;
		result = result && lowerBound.x <= aabb.lowerBound.x;
		result = result && lowerBound.y <= aabb.lowerBound.y;
		result = result && aabb.upperBound.x <= upperBound.x;
		result = result && aabb.upperBound.y <= upperBound.y;
		return result;
	}
	/**************************************************************************
	* 功能描述:光線投射
	* 參數說明: output:光線輸出數據引用
	             input :光線輸入數據引用
	* 返 回 值: true :碰撞
	             false:不碰撞
	***************************************************************************/
	bool RayCast(b2RayCastOutput* output, const b2RayCastInput& input) const;

	b2Vec2 lowerBound;	//lower頂點
	b2Vec2 upperBound;	//upper頂點
};

對於b2ContactFeature和b2ContactID來說主要是和接觸相關的結構體,用於保存兩形狀碰撞時產生的碰撞點的相關信息。對於b2ManifoldPoint、b2Manifold、b2WorldManifold、b2PointState、b2ClipVertex主要用於流形(關於流形 參照 http://zh.wikipedia.org/wiki/流形)相關的結構體,關於具體信息,請看上面的註釋。對於b2RayCastInput和b2RayCastOutput結構,這個大家應該很眼熟吧,主要是在光線投射下,保存光線輸入和輸出數據的。再看看我們更眼熟的b2AABB結構體(小樣,原來你的家在這兒,這下你跑不了了吧,嘿嘿),這裏有些功能函數的實現,在此也不多說了。


3、全局函數的聲明

/**************************************************************************
* 功能描述:通過兩個流形計算點的狀態。這些狀態與從manifold1到maniflod2的過渡有關
            所以state1要麼是持續更新要麼就是刪除
            state2要麼是添加要麼是持續更新
* 參數說明: state1   :狀態1,用於保存mainfold1中接觸點的狀態
             state2   :狀態2,用於保存mainfold2中接觸點的狀態
			 manifold1:流形1
			 manifold2:流形2
* 返 回 值: (void)
***************************************************************************/
void b2GetPointStates(b2PointState state1[b2_maxManifoldPoints], b2PointState state2[b2_maxManifoldPoints],
					  const b2Manifold* manifold1, const b2Manifold* manifold2);
/**************************************************************************
* 功能描述:求兩個圓形成的碰撞流形
* 參數說明: manifold :流形對象的指針
             circleA  :圓形A對象指針
			 xfA      :變換A對象引用
			 circleB  :圓形B對象指針
			 xfB      :變換B對象引用
* 返 回 值: (void)
***************************************************************************/
void b2CollideCircles(b2Manifold* manifold,
					  const b2CircleShape* circleA, const b2Transform& xfA,
					  const b2CircleShape* circleB, const b2Transform& xfB);

/**************************************************************************
* 功能描述:求一個多邊形和一個圓形成的碰撞流形
* 參數說明: manifold :流形對象的指針
             polygonA :多邊形A對象指針
			 xfA      :變換A對象引用
			 circleB  :圓形B對象指針
			 xfB      :變換B對象引用
* 返 回 值: (void)
***************************************************************************/
void b2CollidePolygonAndCircle(b2Manifold* manifold,
							   const b2PolygonShape* polygonA, const b2Transform& xfA,
							   const b2CircleShape* circleB, const b2Transform& xfB);

/**************************************************************************
* 功能描述:求解兩個多邊形碰撞產生的流形
* 參數說明: manifold:碰撞流形指針,用於保存兩個圓產生的流形
             polygonA:多邊形A指針
			 xfA     :變換A
			 polygonB:多邊形B指針
			 xfB     :變換B
* 返 回 值: (void)
***************************************************************************/
void b2CollidePolygons(b2Manifold* manifold,
					   const b2PolygonShape* polygonA, const b2Transform& xfA,
					   const b2PolygonShape* polygonB, const b2Transform& xfB);
/**************************************************************************
* 功能描述:求解一個邊緣形狀和一個圓碰撞產生的流形
* 參數說明: manifold:碰撞流形指針,用於保存兩個圓產生的流形
             polygonA:多邊形A指針
			 xfA     :變換A
			 polygonB:多邊形B指針
			 xfB     :變換B
* 返 回 值: (void)
***************************************************************************/
void b2CollideEdgeAndCircle(b2Manifold* manifold,
							   const b2EdgeShape* polygonA, const b2Transform& xfA,
							   const b2CircleShape* circleB, const b2Transform& xfB);
/**************************************************************************
* 功能描述:求解一個邊緣形狀和一個多邊形碰撞產生的流形
* 參數說明: manifold:碰撞流形指針,用於保存兩個圓產生的流形
             edgeA   :邊緣形狀A指針
			 xfA     :變換A
			 polygonB:多邊形B指針
			 xfB     :變換B
* 返 回 值: (void)
***************************************************************************/
void b2CollideEdgeAndPolygon(b2Manifold* manifold,
							   const b2EdgeShape* edgeA, const b2Transform& xfA,
							   const b2PolygonShape* circleB, const b2Transform& xfB);


/**************************************************************************
* 功能描述:裁剪碰撞流形
* 參數說明: vOut        :裁剪頂點輸出數組
             vIn         :裁剪頂點輸入數組
			 normal      :法向量
			 offset      :偏移量
			 vertexIndexA:頂點索引
* 返 回 值: 輸出頂點的個數
***************************************************************************/
int32 b2ClipSegmentToLine(b2ClipVertex vOut[2], const b2ClipVertex vIn[2],
							const b2Vec2& normal, float32 offset, int32 vertexIndexA);
/**************************************************************************
* 功能描述:測試兩個通用的形狀是否重疊。
            通過距離【Distance】判斷是否重疊
* 參數說明: shapeA :形狀A
             indexA :索引A
			 shapeB :形狀B
			 indexB :索引B
			 xfA    :變換A
			 xfB    : 變換B
* 返 回 值:true    :重疊
            false   :不重疊
***************************************************************************/
bool b2TestOverlap(	const b2Shape* shapeA, int32 indexA,
					const b2Shape* shapeB, int32 indexB,
					const b2Transform& xfA, const b2Transform& xfB);

此部分,註釋已經說的很清楚了,在此也不贅述了。

 

4、內聯函數的實現

//驗證邊界排序是否有效
inline bool b2AABB::IsValid() const
{
	b2Vec2 d = upperBound - lowerBound;
	bool valid = d.x >= 0.0f && d.y >= 0.0f;
	valid = valid && lowerBound.IsValid() && upperBound.IsValid();
	return valid;
}
/**************************************************************************
* 功能描述:測試兩個通用的形狀是否重疊。
            通過aabb判斷是否重疊
* 參數說明: a :AABB對象的引用
             b :AABB對象的引用
* 返 回 值: true :重疊
             false:不重疊
***************************************************************************/
inline bool b2TestOverlap(const b2AABB& a, const b2AABB& b)
{
	b2Vec2 d1, d2;
	d1 = b.lowerBound - a.upperBound;
	d2 = a.lowerBound - b.upperBound;

	if (d1.x > 0.0f || d1.y > 0.0f)
		return false;

	if (d2.x > 0.0f || d2.y > 0.0f)
		return false;

	return true;
}

對於b2TestOverlap函數,我們可以看到此處進行了函數的重載,對於上一個TestOverlap函數是通過距離【Distance】判斷是否重疊的,而這個函數則是通過通過aabb判斷是否重疊。兩者用不同的方法,卻實現了相同的效果,但是從某些方面來說通過距離判斷是否重疊的話更加精確一點,但效率方面卻要第一點。爲此,我們可以根據用戶的不同要求讓用戶選擇不同的函數,如用戶要求精確度高,同時效率方面不是太在乎的我們可以選擇通過距離判斷的那種,相反,我們可以選擇通過aabb判斷的那種。

 

我們再來看看b2Collision.cpp文件,還是上源碼:

//根據流形和提供的變換初始化此結構體。
void b2WorldManifold::Initialize(const b2Manifold* manifold,
						  const b2Transform& xfA, float32 radiusA,
						  const b2Transform& xfB, float32 radiusB)
{
	//判斷流形的點
	if (manifold->pointCount == 0)
	{
		return;
	}
	//獲取流形的類型
	switch (manifold->type)
	{
	case b2Manifold::e_circles:                                   //圓形
		{
			//設置法向量
			normal.Set(1.0f, 0.0f);
			b2Vec2 pointA = b2Mul(xfA, manifold->localPoint);
			b2Vec2 pointB = b2Mul(xfB, manifold->points[0].localPoint);
			//判斷兩點是否重合
			if (b2DistanceSquared(pointA, pointB) > b2_epsilon * b2_epsilon)
			{
				//獲取法向量,並標準化
				normal = pointB - pointA;
				normal.Normalize();
			}
			//獲取世界接觸點
			b2Vec2 cA = pointA + radiusA * normal;
			b2Vec2 cB = pointB - radiusB * normal;
			points[0] = 0.5f * (cA + cB);
		}
		break;

	case b2Manifold::e_faceA:                                   //面A
		{
			//
			normal = b2Mul(xfA.q, manifold->localNormal);
			b2Vec2 planePoint = b2Mul(xfA, manifold->localPoint);
			//獲取世界接觸點
			for (int32 i = 0; i < manifold->pointCount; ++i)
			{
				b2Vec2 clipPoint = b2Mul(xfB, manifold->points[i].localPoint);
				b2Vec2 cA = clipPoint + (radiusA - b2Dot(clipPoint - planePoint, normal)) * normal;
				b2Vec2 cB = clipPoint - radiusB * normal;
				points[i] = 0.5f * (cA + cB);
			}
		}
		break;

	case b2Manifold::e_faceB:                                 //面B
		{
			normal = b2Mul(xfB.q, manifold->localNormal);
			b2Vec2 planePoint = b2Mul(xfB, manifold->localPoint);
			//獲取世界接觸點
			for (int32 i = 0; i < manifold->pointCount; ++i)
			{
				b2Vec2 clipPoint = b2Mul(xfA, manifold->points[i].localPoint);
				b2Vec2 cB = clipPoint + (radiusB - b2Dot(clipPoint - planePoint, normal)) * normal;
				b2Vec2 cA = clipPoint - radiusA * normal;
				points[i] = 0.5f * (cA + cB);
			}
			// 保證法向量的頂點是A到B的
			normal = -normal;
		}
		break;
	}
}
//通過給定的兩個流形計算點的狀態。
void b2GetPointStates(b2PointState state1[b2_maxManifoldPoints], b2PointState state2[b2_maxManifoldPoints],
					  const b2Manifold* manifold1, const b2Manifold* manifold2)
{
	//置空初始狀態
	for (int32 i = 0; i < b2_maxManifoldPoints; ++i)
	{
		state1[i] = b2_nullState;
		state2[i] = b2_nullState;
	}
	//遍歷maifold1檢測狀態的持續和刪除
	for (int32 i = 0; i < manifold1->pointCount; ++i)
	{
		//獲取mainfold1的接觸id
		b2ContactID id = manifold1->points[i].id;
		//將狀態置爲刪除狀態
		state1[i] = b2_removeState;
		//遍歷manifold2檢測是否有接觸存在
		//若有則修改當前狀態爲持續
		for (int32 j = 0; j < manifold2->pointCount; ++j)
		{
			//接觸點是否相等
			if (manifold2->points[j].id.key == id.key)
			{
				//改變接觸狀態
				state1[i] = b2_persistState;
				break;
			}
		}
	}

	//遍歷maifold1檢測狀態的持續和添加
	for (int32 i = 0; i < manifold2->pointCount; ++i)
	{
		//獲取mainfold2的接觸id
		b2ContactID id = manifold2->points[i].id;
		//將狀態置爲添加狀態
		state2[i] = b2_addState;
		//遍歷manifold1檢測是否有接觸存在
		//若有則修改當前狀態爲持續
		for (int32 j = 0; j < manifold1->pointCount; ++j)
		{
			//接觸點是否相等
			if (manifold1->points[j].id.key == id.key)
			{
				//改變接觸狀態
				state2[i] = b2_persistState;
				break;
			}
		}
	}
}
//光線投射
bool b2AABB::RayCast(b2RayCastOutput* output, const b2RayCastInput& input) const
{
	//獲取float的邊值
	float32 tmin = -b2_maxFloat;
	float32 tmax = b2_maxFloat;
	//獲取差值並用b2Abs取它的絕對值
	b2Vec2 p = input.p1;
	b2Vec2 d = input.p2 - input.p1;
	b2Vec2 absD = b2Abs(d);
	//平面的法線
	b2Vec2 normal;

	for (int32 i = 0; i < 2; ++i)
	{
		if (absD(i) < b2_epsilon)
		{
			// 平行
			if (p(i) < lowerBound(i) || upperBound(i) < p(i))
			{
				return false;
			}
		}
		else
		{
			float32 inv_d = 1.0f / d(i);
			float32 t1 = (lowerBound(i) - p(i)) * inv_d;
			float32 t2 = (upperBound(i) - p(i)) * inv_d;

			//法向量方向
			float32 s = -1.0f;

			if (t1 > t2)
			{
				b2Swap(t1, t2);
				s = 1.0f;
			}
			// 提升最小值
			if (t1 > tmin)
			{
				normal.SetZero();
				normal(i) = s;
				tmin = t1;
			}
			// 下降最大值
			tmax = b2Min(tmax, t2);

			if (tmin > tmax)
			{
				return false;
			}
		}
	}
	//驗證tmin的有效值
	//光線的起始點在盒子內部
	//或者光線的相交不在maxfraction範圍之內
	if (tmin < 0.0f || input.maxFraction < tmin)
	{
		return false;
	}
	//保存交點信息
	output->fraction = tmin;
	output->normal = normal;
	return true;
}

// Sutherland-Hodgman裁剪. 參見 http://en.wikipedia.org/wiki/Sutherland-Hodgman
int32 b2ClipSegmentToLine(b2ClipVertex vOut[2], const b2ClipVertex vIn[2],
						const b2Vec2& normal, float32 offset, int32 vertexIndexA)
{
	// 開始時沒有輸出點
	int32 numOut = 0;
	//計算線與末尾點的距離
	float32 distance0 = b2Dot(normal, vIn[0].v) - offset;
	float32 distance1 = b2Dot(normal, vIn[1].v) - offset;
	// 點都在平面的一邊
	if (distance0 <= 0.0f) vOut[numOut++] = vIn[0];
	if (distance1 <= 0.0f) vOut[numOut++] = vIn[1];

	// 點在平面的兩邊
	if (distance0 * distance1 < 0.0f)
	{
		// 查找邊緣與平面的交叉點
		float32 interp = distance0 / (distance0 - distance1);
		vOut[numOut].v = vIn[0].v + interp * (vIn[1].v - vIn[0].v);
		// VertexA 觸碰到edgeB
		vOut[numOut].id.cf.indexA = vertexIndexA;
		vOut[numOut].id.cf.indexB = vIn[0].id.cf.indexB;
		vOut[numOut].id.cf.typeA = b2ContactFeature::e_vertex;
		vOut[numOut].id.cf.typeB = b2ContactFeature::e_face;
		++numOut;
	}
	//輸出點個數
	return numOut;
}
//測試兩個通用的形狀是否重疊。
bool b2TestOverlap(	const b2Shape* shapeA, int32 indexA,
					const b2Shape* shapeB, int32 indexB,
					const b2Transform& xfA, const b2Transform& xfB)
{
	//初始化input變量
	b2DistanceInput input;
	input.proxyA.Set(shapeA, indexA);
	input.proxyB.Set(shapeB, indexB);
	input.transformA = xfA;
	input.transformB = xfB;
	input.useRadii = true;
	//聲明cache對象
	b2SimplexCache cache;
	cache.count = 0;
	//聲明output變量,並獲取output
	b2DistanceOutput output;

	b2Distance(&output, &cache, &input);
	//判斷是否重疊,並返回
	return output.distance < 10.0f * b2_epsilon;
}

本部分主要4個函數,Initialize函數初始化世界流形(當前狀態下的流形)。


b2GetPointStates函數則是重新更新接觸點的狀態,這個函數是到程序員要用的時候自己手動調用的。


RayCast函數則用於光線投射,在這裏,我們注意下那個for循環,有個問題,大家先思考一下,爲什麼i的終止值小於2呢?那個2究竟是幹什麼的?


b2ClipSegmentToLine函數是裁剪碰撞流形,這裏用到了Sutherland-Hodgman裁剪,(參見 http://en.wikipedia.org/wiki/Sutherland-Hodgman),關於Sutherland-Hodgman裁剪算法,我們簡單的說一下,這種方法也叫逐邊裁減法,主要思想是:將多邊形的各邊先相對於窗口的某一條邊界進行裁剪,然後將裁剪結果再與另一條邊界進行裁剪,如此重複多次,便可得到最終結果。再來說說我們是怎麼做的:

  1. 由函數傳入相對與法線的偏移量offset,將法線加偏移量作爲邊界,將平面分成兩部分:可見部分和不可見部分。
  2. 在獲取兩個輸入點向量在法線方向上的長度,並和邊界做比較相減,獲取結果。
  3. 然後判斷結果和是否有交點,並保存到數組中。

因爲此處我們只有兩個點,故不需要多次重複裁剪。

 

b2TestOverlap函數主要是通過判斷距離來實現是否重疊的,在此就不多說了。


大家知道上面那個問題的答案了嗎?

知道了?!太好了,那我就不說了。嘿嘿。。。


ps:

 

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








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