本系列博客是由扭曲45原创,欢迎转载,转载时注明出处,http://blog.csdn.net/cg0206/article/details/8441463

Box2d中,要形状间实现碰撞,必须两个碰撞形状中至少有一个形状要有体积,而链形状每条边都被看作一个边缘形状,此时我们只要实现圆形、多边形、边缘三个具体形状间的碰撞,因为边缘形状没有体积,故不存在边缘与边缘之间的碰撞。剩下还有边缘和圆,边缘和多边形,圆和圆,圆和多边形,多边形和多边形等这5种,我们将这5中分成如下三类:

1、 边缘形状有关的碰撞。即边缘与圆,边缘与多边形

2、 圆形形状有关的碰撞。即圆和圆,圆和多边形

3、 多边形形状有关的碰撞。即多边形和多边形

它们如何实现的呢?好,我们现在就慢慢道来。

1、 边缘形状有关的碰撞。

这部分我们将它分成三部分边缘与圆、边缘与多边形辅助结构体的定义、边缘与多边形碰撞检测相关函数的实现

a)、边缘与圆

/**************************************************************************
* 功能描述: 在边缘形状和圆形之间获得流形 所有的边都是连通的
* 参数说明: manifold :流形的指针,用于获得两个多边形碰撞而形成的流形edgeA    :边缘形状A对象指针xfA      :变换A引用circleB  :圆形B对象指针xfB      :变换B引用
* 返 回 值:(void)
***************************************************************************/
void b2CollideEdgeAndCircle(b2Manifold* manifold,const b2EdgeShape* edgeA, const b2Transform& xfA,const b2CircleShape* circleB, const b2Transform& xfB)
{manifold->pointCount = 0;// 在边缘形状的外框处理圆形b2Vec2 Q = b2MulT(xfA, b2Mul(xfB, circleB->m_p));b2Vec2 A = edgeA->m_vertex1, B = edgeA->m_vertex2;b2Vec2 e = B - A;// 计算向量AB与两端点与圆心Q组成的向量的点乘// 结果可以判断两向量组成的夹角的大小,从而判断哪个点离圆更近float32 u = b2Dot(e, B - Q);float32 v = b2Dot(e, Q - A);//获取圆与边缘形状的半径和float32 radius = edgeA->m_radius + circleB->m_radius;//声明接触特征并初始化b2ContactFeature cf;cf.indexB = 0;cf.typeB = b2ContactFeature::e_vertex;// A区域,即A离圆点更近if (v <= 0.0f){b2Vec2 P = A;//获取a点到圆的间距b2Vec2 d = Q - P;float32 dd = b2Dot(d, d);//间距大于两形状的半径//则两形状之间没有碰撞if (dd > radius * radius){return;}// 这里的边是否连接A,即在A的头部还有个辅助点if (edgeA->m_hasVertex0){b2Vec2 A1 = edgeA->m_vertex0;b2Vec2 B1 = A;b2Vec2 e1 = B1 - A1;float32 u1 = b2Dot(e1, B1 - Q);// 圆是否在前一个边的AB区域if (u1 > 0.0f){return;}}//获取流形的相关信息cf.indexA = 0;cf.typeA = b2ContactFeature::e_vertex;manifold->pointCount = 1;manifold->type = b2Manifold::e_circles;manifold->localNormal.SetZero();manifold->localPoint = P;manifold->points[0].id.key = 0;manifold->points[0].id.cf = cf;manifold->points[0].localPoint = circleB->m_p;return;}// B区域,即B点离圆点更近if (u <= 0.0f){b2Vec2 P = B;//获取b点到圆的间距b2Vec2 d = Q - P;float32 dd = b2Dot(d, d);//间距大于两形状的半径//则两形状之间没有碰撞if (dd > radius * radius){return;}// 这里的边是否连接Bif (edgeA->m_hasVertex3){b2Vec2 B2 = edgeA->m_vertex3;b2Vec2 A2 = B;b2Vec2 e2 = B2 - A2;float32 v2 = b2Dot(e2, Q - A2);// 圆是否在下一个边的AB区域if (v2 > 0.0f){return;}}//获取流形的相关信息cf.indexA = 1;cf.typeA = b2ContactFeature::e_vertex;manifold->pointCount = 1;manifold->type = b2Manifold::e_circles;manifold->localNormal.SetZero();manifold->localPoint = P;manifold->points[0].id.key = 0;manifold->points[0].id.cf = cf;manifold->points[0].localPoint = circleB->m_p;return;}// 区域AB,即AB两顶点到圆心的距离相等// 获取等分点p,再计算p的长度,与半间比较float32 den = b2Dot(e, e);b2Assert(den > 0.0f);b2Vec2 P = (1.0f / den) * (u * A + v * B);b2Vec2 d = Q - P;float32 dd = b2Dot(d, d);//间距大于两形状的半径//则两形状之间没有碰撞if (dd > radius * radius){return;}//构造AB边的法向量,要求于AQ向量的点乘为正b2Vec2 n(-e.y, e.x);if (b2Dot(n, Q - A) < 0.0f){n.Set(-n.x, -n.y);}n.Normalize();//获取流形的相关信息cf.indexA = 0;cf.typeA = b2ContactFeature::e_face;manifold->pointCount = 1;manifold->type = b2Manifold::e_faceA;manifold->localNormal = n;manifold->localPoint = A;manifold->points[0].id.key = 0;manifold->points[0].id.cf = cf;manifold->points[0].localPoint = circleB->m_p;
}

对于b2CollideEdgeAndCircle主要分为三种情况,如图1:

然后对每个区域中圆形到最近的点进行计算长度,然后判断是否符合要求,同时保存相关信息到流形中。

b)、边缘与多边形辅助结构体的定义

// 分离轴信息结构体,这个结构体用于跟踪最好的分离轴
struct b2EPAxis
{enum Type{e_unknown,           //无类型e_edgeA,             //从边缘形状中获得b2EPAxise_edgeB              //从多边形  中获得b2EPAxis};Type type;               //类型变量int32 index;             //顶点索引float32 separation;      //间距值
};//临时多边形
struct b2TempPolygon
{b2Vec2 vertices[b2_maxPolygonVertices];   //顶点数组b2Vec2 normals[b2_maxPolygonVertices];    //法向量int32 count;                              //顶点数量
};// 参照面用于剪裁
struct b2ReferenceFace
{int32 i1, i2;                             //索引1、2b2Vec2 v1, v2;                            //顶点1、2b2Vec2 normal;                            //法向量b2Vec2 sideNormal1;                       //侧面1的法向量float32 sideOffset1;                      //侧面1的偏移量b2Vec2 sideNormal2;                       //侧面2的法向量float32 sideOffset2;                      //侧面2的偏移量
};// 这个类碰撞是一个边缘形状和一个多边形,告诉计算相邻边
struct b2EPCollider
{/*************************************************************************** 功能描述: 检查一个边缘形状和一个多边形的碰撞,获取流形* 参数说明: manifold:流形的指针edgeA   :边缘形状A的指针xfA     :变换AploygonB:多边形B的指针xfB     :变换B* 返 回 值: (void)***************************************************************************/void Collide(b2Manifold* manifold, const b2EdgeShape* edgeA, const b2Transform& xfA,const b2PolygonShape* polygonB, const b2Transform& xfB);/*************************************************************************** 功能描述: 计算边缘形状的间距* 参数说明: (void)* 返 回 值: 分离轴信息结构体***************************************************************************/b2EPAxis ComputeEdgeSeparation();/*************************************************************************** 功能描述: 计算多边形的间距* 参数说明: (void)* 返 回 值: 分离轴信息结构体***************************************************************************/b2EPAxis ComputePolygonSeparation();enum VertexType{e_isolated,                                //孤立的e_concave,                                 //凹的e_convex                                   //凸的};b2TempPolygon m_polygonB;                      //临时多边形平面,用来保存多边形的相关信息b2Transform m_xf;                              //变换b2Vec2 m_centroidB;                            //转换后多边形的质心b2Vec2 m_v0, m_v1, m_v2, m_v3;                 //边缘形状每一个边的顶点b2Vec2 m_normal0, m_normal1, m_normal2;        //边缘形状每一个边的法向量b2Vec2 m_normal;                               //边缘形状的法向量VertexType m_type1, m_type2;                   //角的类型b2Vec2 m_lowerLimit, m_upperLimit;             //最高和最低限制float32 m_radius;                              //半径bool m_front;                                  //是否在前面
};

对于结构体的定义,这部分主要定义了四个相关的结构体用于保存边缘形状和多边形之间的相关碰撞信息。我们一下来看下。b2EPAxis主要用于保存查到的分离轴(参见http://blog.csdn.net/bugrunner/article/details/5727256)信息,b2TempPolygon主要用于保存临时多边形,b2ReferenceFace主要用于保存剪裁参照面信息的。b2EPCollider主要用于处理边缘形状和多边形之间碰撞检测的。

c)、边缘与多边形碰撞检测相关函数的实现

//检查一个边缘形状和一个多边形的碰撞,获取流形
void b2EPCollider::Collide(b2Manifold* manifold, const b2EdgeShape* edgeA, const b2Transform& xfA,const b2PolygonShape* polygonB, const b2Transform& xfB)
{m_xf = b2MulT(xfA, xfB);m_centroidB = b2Mul(m_xf, polygonB->m_centroid);//边缘形状的四个顶点m_v0 = edgeA->m_vertex0;m_v1 = edgeA->m_vertex1;m_v2 = edgeA->m_vertex2;m_v3 = edgeA->m_vertex3;//边缘形状是否含有相邻点bool hasVertex0 = edgeA->m_hasVertex0;bool hasVertex3 = edgeA->m_hasVertex3;//获取边向量,并标准化边向量b2Vec2 edge1 = m_v2 - m_v1;edge1.Normalize();//获取边1的法向量m_normal1.Set(edge1.y, -edge1.x);//获取法向量与质心的偏移量float32 offset1 = b2Dot(m_normal1, m_centroidB - m_v1);float32 offset0 = 0.0f, offset2 = 0.0f;bool convex1 = false, convex2 = false;// 是否前面有边if (hasVertex0){//获取边0的向量,标准化边向量b2Vec2 edge0 = m_v1 - m_v0;edge0.Normalize();//获取边0的法向量m_normal0.Set(edge0.y, -edge0.x);// 是否是凸角convex1 = b2Cross(edge0, edge1) >= 0.0f;//计算偏移量offset0 = b2Dot(m_normal0, m_centroidB - m_v0);}// 是否后面有边if (hasVertex3){//获取边2的向量,标准化边向量b2Vec2 edge2 = m_v3 - m_v2;edge2.Normalize();//获取边2的法向量m_normal2.Set(edge2.y, -edge2.x);//是否是凸角convex2 = b2Cross(edge1, edge2) > 0.0f;offset2 = b2Dot(m_normal2, m_centroidB - m_v2);}// 决定前面或者后面碰撞。决定碰撞法线限制// 含有两个辅助顶点if (hasVertex0 && hasVertex3){//两个都是凸角if (convex1 && convex2){m_front = offset0 >= 0.0f || offset1 >= 0.0f || offset2 >= 0.0f;//前面if (m_front){//法向量m_normal = m_normal1;m_lowerLimit = m_normal0;m_upperLimit = m_normal2;}else{m_normal = -m_normal1;m_lowerLimit = -m_normal1;m_upperLimit = -m_normal1;}}//edge0和edge1形成的是凸角else if (convex1){m_front = offset0 >= 0.0f || (offset1 >= 0.0f && offset2 >= 0.0f);if (m_front){m_normal = m_normal1;m_lowerLimit = m_normal0;m_upperLimit = m_normal1;}else{m_normal = -m_normal1;m_lowerLimit = -m_normal2;m_upperLimit = -m_normal1;}}//edge2和edge3形成的是凸角else if (convex2){m_front = offset2 >= 0.0f || (offset0 >= 0.0f && offset1 >= 0.0f);if (m_front){m_normal = m_normal1;m_lowerLimit = m_normal1;m_upperLimit = m_normal2;}else{m_normal = -m_normal1;m_lowerLimit = -m_normal1;m_upperLimit = -m_normal0;}}else{//前面m_front = offset0 >= 0.0f && offset1 >= 0.0f && offset2 >= 0.0f;if (m_front){m_normal = m_normal1;m_lowerLimit = m_normal1;m_upperLimit = m_normal1;}else{m_normal = -m_normal1;m_lowerLimit = -m_normal2;m_upperLimit = -m_normal0;}}}//含有一个辅助顶点m_v0else if (hasVertex0){//凸角if (convex1){m_front = offset0 >= 0.0f || offset1 >= 0.0f;if (m_front){m_normal = m_normal1;m_lowerLimit = m_normal0;m_upperLimit = -m_normal1;}else{m_normal = -m_normal1;m_lowerLimit = m_normal1;m_upperLimit = -m_normal1;}}else{m_front = offset0 >= 0.0f && offset1 >= 0.0f;if (m_front){m_normal = m_normal1;m_lowerLimit = m_normal1;m_upperLimit = -m_normal1;}else{m_normal = -m_normal1;m_lowerLimit = m_normal1;m_upperLimit = -m_normal0;}}}//含有一个辅助顶点m_v3else if (hasVertex3){//凸角if (convex2){m_front = offset1 >= 0.0f || offset2 >= 0.0f;if (m_front){m_normal = m_normal1;m_lowerLimit = -m_normal1;m_upperLimit = m_normal2;}else{m_normal = -m_normal1;m_lowerLimit = -m_normal1;m_upperLimit = m_normal1;}}else{m_front = offset1 >= 0.0f && offset2 >= 0.0f;if (m_front){m_normal = m_normal1;m_lowerLimit = -m_normal1;m_upperLimit = m_normal1;}else{m_normal = -m_normal1;m_lowerLimit = -m_normal2;m_upperLimit = m_normal1;}}       }//不含有辅助顶点else{m_front = offset1 >= 0.0f;if (m_front){m_normal = m_normal1;m_lowerLimit = -m_normal1;m_upperLimit = -m_normal1;}else{m_normal = -m_normal1;m_lowerLimit = m_normal1;m_upperLimit = m_normal1;}}// 在A框架内获取polygonBm_polygonB.count = polygonB->m_vertexCount;for (int32 i = 0; i < polygonB->m_vertexCount; ++i){m_polygonB.vertices[i] = b2Mul(m_xf, polygonB->m_vertices[i]);m_polygonB.normals[i] = b2Mul(m_xf.q, polygonB->m_normals[i]);}//外壳半径m_radius = 2.0f * b2_polygonRadius;manifold->pointCount = 0;b2EPAxis edgeAxis = ComputeEdgeSeparation();// 如果没有发现有效的分离轴,说明这个边没有碰撞if (edgeAxis.type == b2EPAxis::e_unknown){return;}//如果间距不小于多边形外壳半径,则返回if (edgeAxis.separation > m_radius){return;}//获取多边形分离轴记录结构体b2EPAxis polygonAxis = ComputePolygonSeparation();//多边形分离轴记录类型不为空,或者间距不小于其半径,则返回if (polygonAxis.type != b2EPAxis::e_unknown && polygonAxis.separation > m_radius){return;}// 用滞后现象进行抖动减少const float32 k_relativeTol = 0.98f;const float32 k_absoluteTol = 0.001f;b2EPAxis primaryAxis;if (polygonAxis.type == b2EPAxis::e_unknown){primaryAxis = edgeAxis;}else if (polygonAxis.separation > k_relativeTol * edgeAxis.separation + k_absoluteTol){primaryAxis = polygonAxis;}else{primaryAxis = edgeAxis;}//剪裁顶点b2ClipVertex ie[2];//剪裁面b2ReferenceFace rf;if (primaryAxis.type == b2EPAxis::e_edgeA){manifold->type = b2Manifold::e_faceA;// 查找多边形法向量,它几乎是边缘形状法线的反平行int32 bestIndex = 0;float32 bestValue = b2Dot(m_normal, m_polygonB.normals[0]);for (int32 i = 1; i < m_polygonB.count; ++i){float32 value = b2Dot(m_normal, m_polygonB.normals[i]);if (value < bestValue){bestValue = value;bestIndex = i;}}int32 i1 = bestIndex;int32 i2 = i1 + 1 < m_polygonB.count ? i1 + 1 : 0;//初始化剪裁点信息ie[0].v = m_polygonB.vertices[i1];ie[0].id.cf.indexA = 0;ie[0].id.cf.indexB = i1;ie[0].id.cf.typeA = b2ContactFeature::e_face;ie[0].id.cf.typeB = b2ContactFeature::e_vertex;ie[1].v = m_polygonB.vertices[i2];ie[1].id.cf.indexA = 0;ie[1].id.cf.indexB = i2;ie[1].id.cf.typeA = b2ContactFeature::e_face;ie[1].id.cf.typeB = b2ContactFeature::e_vertex;//正面if (m_front){//初始化剪裁面rf.i1 = 0;rf.i2 = 1;rf.v1 = m_v1;rf.v2 = m_v2;rf.normal = m_normal1;}else{//初始化剪裁面rf.i1 = 1;rf.i2 = 0;rf.v1 = m_v2;rf.v2 = m_v1;rf.normal = -m_normal1;}     }else{//赋值流形类型、剪裁顶点manifold->type = b2Manifold::e_faceB;ie[0].v = m_v1;ie[0].id.cf.indexA = 0;ie[0].id.cf.indexB = primaryAxis.index;ie[0].id.cf.typeA = b2ContactFeature::e_vertex;ie[0].id.cf.typeB = b2ContactFeature::e_face;ie[1].v = m_v2;ie[1].id.cf.indexA = 0;ie[1].id.cf.indexB = primaryAxis.index;      ie[1].id.cf.typeA = b2ContactFeature::e_vertex;ie[1].id.cf.typeB = b2ContactFeature::e_face;//赋值剪裁面rf.i1 = primaryAxis.index;rf.i2 = rf.i1 + 1 < m_polygonB.count ? rf.i1 + 1 : 0;rf.v1 = m_polygonB.vertices[rf.i1];rf.v2 = m_polygonB.vertices[rf.i2];rf.normal = m_polygonB.normals[rf.i1];}//赋值剪裁面rf.sideNormal1.Set(rf.normal.y, -rf.normal.x);rf.sideNormal2 = -rf.sideNormal1;rf.sideOffset1 = b2Dot(rf.sideNormal1, rf.v1);rf.sideOffset2 = b2Dot(rf.sideNormal2, rf.v2);// 剪裁入射线扩展到edge1的其它边b2ClipVertex clipPoints1[2];b2ClipVertex clipPoints2[2];int32 np;// 剪裁盒子的侧面1np = b2ClipSegmentToLine(clipPoints1, ie, rf.sideNormal1, rf.sideOffset1, rf.i1);if (np < b2_maxManifoldPoints){return;}// 剪裁盒子的另一个侧面np = b2ClipSegmentToLine(clipPoints2, clipPoints1, rf.sideNormal2, rf.sideOffset2, rf.i2);if (np < b2_maxManifoldPoints){return;}// 现在 clipPoints2 包含裁剪点if (primaryAxis.type == b2EPAxis::e_edgeA){manifold->localNormal = rf.normal;manifold->localPoint = rf.v1;}else{manifold->localNormal = polygonB->m_normals[rf.i1];manifold->localPoint = polygonB->m_vertices[rf.i1];}int32 pointCount = 0;遍历流形的所有的顶点for (int32 i = 0; i < b2_maxManifoldPoints; ++i){float32 separation;//间距separation = b2Dot(rf.normal, clipPoints2[i].v - rf.v1);//相交if (separation <= m_radius){//向现有的流行中添加流形接触点b2ManifoldPoint* cp = manifold->points + pointCount;if (primaryAxis.type == b2EPAxis::e_edgeA){cp->localPoint = b2MulT(m_xf, clipPoints2[i].v);cp->id = clipPoints2[i].id;}else{cp->localPoint = clipPoints2[i].v;cp->id.cf.typeA = clipPoints2[i].id.cf.typeB;cp->id.cf.typeB = clipPoints2[i].id.cf.typeA;cp->id.cf.indexA = clipPoints2[i].id.cf.indexB;cp->id.cf.indexB = clipPoints2[i].id.cf.indexA;}++pointCount;}}//更新流形顶点数量manifold->pointCount = pointCount;
}
//计算边缘形状的间距
b2EPAxis b2EPCollider::ComputeEdgeSeparation()
{// 初始化 用于保存分离轴b2EPAxis axis;axis.type = b2EPAxis::e_edgeA;axis.index = m_front ? 0 : 1;axis.separation = FLT_MAX;//遍历//获取各个顶点在其边缘形状法向量方向上的投影//获取最小间距for (int32 i = 0; i < m_polygonB.count; ++i){float32 s = b2Dot(m_normal, m_polygonB.vertices[i] - m_v1);if (s < axis.separation){//获取间距axis.separation = s;}}return axis;
}
//计算多边形的间距
b2EPAxis b2EPCollider::ComputePolygonSeparation()
{//初始化 用于保存分离轴b2EPAxis axis;axis.type = b2EPAxis::e_unknown;axis.index = -1;axis.separation = -FLT_MAX;//获取法向量的反垂直向量b2Vec2 perp(-m_normal.y, m_normal.x);//遍历多边形B的顶点//获取每个点到边缘形状两点组成的向量在多边形边对应的法向量上的投影长度for (int32 i = 0; i < m_polygonB.count; ++i){b2Vec2 n = -m_polygonB.normals[i];//分别获取边缘顶点到多边形顶点所组成的向量在多边形边对应的法向量上的投影长度float32 s1 = b2Dot(n, m_polygonB.vertices[i] - m_v1);float32 s2 = b2Dot(n, m_polygonB.vertices[i] - m_v2);float32 s = b2Min(s1, s2);// 若最小距离大于半径,则必不碰撞,退出if (s > m_radius){// 没有碰撞axis.type = b2EPAxis::e_edgeB;axis.index = i;axis.separation = s;return axis;}// 判断方向,邻接if (b2Dot(n, perp) >= 0.0f){if (b2Dot(n - m_upperLimit, m_normal) < -b2_angularSlop){continue;}}else{if (b2Dot(n - m_lowerLimit, m_normal) < -b2_angularSlop){continue;}}//获取最小的间距和分离轴信息if (s > axis.separation){axis.type = b2EPAxis::e_edgeB;axis.index = i;axis.separation = s;}}return axis;
}/**************************************************************************
* 功能描述: 检查一个边缘形状和一个多边形的碰撞,获取流形
* 参数说明: manifold:流形的指针edgeA   :边缘形状A的指针xfA     :变换AploygonB:多边形B的指针xfB     :变换B
* 返 回 值: (void)
***************************************************************************/
void b2CollideEdgeAndPolygon(   b2Manifold* manifold,const b2EdgeShape* edgeA, const b2Transform& xfA,const b2PolygonShape* polygonB, const b2Transform& xfB)
{//声明边缘和多边形结构体变量,并调用碰撞b2EPCollider collider;collider.Collide(manifold, edgeA, xfA, polygonB, xfB);
}

对于这4个函数,我们先来看Collide函数,其中对于边缘形状,如果有辅助点话,我们做了以下几种的判断,如图2:

对于源码中相关的角的判断,和图上四种情形相似,当然还有没有辅助点的、一个辅助点的情形,我们均可以根据上图遮住相关辅助点,进行判断,在此也不多说了。主要步骤有4步:

i)、判断碰撞是在边缘形状的前面还是后面。获取碰撞法线,并决定碰撞法线的限制。如图1中的凹凸角的判断。

ii)、根据获取的法线,分别在多边形和边缘形状上获取分离轴,然后做相关判断,对于边缘形状获取的分离轴,如果是分离轴是无类型或者距离大于多边形外壳半径,则直接返回;如果是边缘形状的分离轴,如果是无类型且距离大于多边形外壳半径,则直接返回。关于外壳半径,如下图图3中,实线与虚线的距离,用于防止单个时间步长内两个多边形瞬间互相穿透的情况的发生。

iii)、根据分离轴获取两个剪裁面。

iiii)、根据获取的剪裁面获得流形相关信息

对于ComputeEdgeSeparation函数遍历多边形B的所有顶点,主要是获取各个边缘形状的各个顶点与边缘形状m_v1形成的向量在其边缘形状法向量方向上的投影,获取最小的投影值并返回。

对于ComputePolygonSeparation函数遍历多边形B的所有顶点,获取每个点到边缘形状两点组成的向量在多边形边对应的法向量上的投影长度,获取最小值并返回。

对于b2CollideEdgeAndPolygon函数主要是调用Collide,来检查一个边缘形状和一个多边形的碰撞,获取流形的。

2、 圆形形状有关的碰撞。即圆和圆,圆和多边形

 /**************************************************************************
* 功能描述:求两个圆形成的碰撞流形
* 参数说明: 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)
{//将流形顶点置0manifold->pointCount = 0;// 为pA、pB赋值,即获取两形状之间的变换后的圆心b2Vec2 pA = b2Mul(xfA, circleA->m_p);b2Vec2 pB = b2Mul(xfB, circleB->m_p);// 向量d,并获取它的长度平方,即两形状之间的圆心的长度平方b2Vec2 d = pB - pA;float32 distSqr = b2Dot(d, d);//获取两形状的半径,并取得两者之和float32 rA = circleA->m_radius, rB = circleB->m_radius;float32 radius = rA + rB;// 圆心之间的距离大于半径之后,两者不相交if (distSqr > radius * radius){return;}//为流形赋值manifold->type = b2Manifold::e_circles;manifold->localPoint = circleA->m_p;manifold->localNormal.SetZero();manifold->pointCount = 1;manifold->points[0].localPoint = circleB->m_p;manifold->points[0].id.key = 0;
}
/**************************************************************************
* 功能描述:求一个多边形和一个圆形成的碰撞流形
* 参数说明: 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->pointCount = 0;// 在多边形的外框上计算圆心位置b2Vec2 c = b2Mul(xfB, circleB->m_p);b2Vec2 cLocal = b2MulT(xfA, c);//查找最小分离边int32 normalIndex = 0;float32 separation = -b2_maxFloat;//获取形状半径之和float32 radius = polygonA->m_radius + circleB->m_radius;int32 vertexCount = polygonA->m_vertexCount;const b2Vec2* vertices = polygonA->m_vertices;const b2Vec2* normals = polygonA->m_normals;//遍历多边形的每条边,//获取边上圆的当前圆心和多边形的每个点在其边上法线方向上的投影长度//并和两形状之间的半径做比较for (int32 i = 0; i < vertexCount; ++i){//获取圆心到多边形顶点组成的向量在多边形法线方向上投影的长度float32 s = b2Dot(normals[i], cLocal - vertices[i]);//不相交,退出if (s > radius){return;}//获取最大的距离if (s > separation){separation = s;normalIndex = i;}}// 获取两个最大距离的两个顶点int32 vertIndex1 = normalIndex;int32 vertIndex2 = vertIndex1 + 1 < vertexCount ? vertIndex1 + 1 : 0;b2Vec2 v1 = vertices[vertIndex1];b2Vec2 v2 = vertices[vertIndex2];// 如果距离在误差之内,则为形成的流形赋值并退出if (separation < b2_epsilon){manifold->pointCount = 1;manifold->type = b2Manifold::e_faceA;manifold->localNormal = normals[normalIndex];manifold->localPoint = 0.5f * (v1 + v2);manifold->points[0].localPoint = circleB->m_p;manifold->points[0].id.key = 0;return;}//1、计算局部圆心cLocal与顶点组成的向量与多边形边向量之间的点乘//2、结果可以判断两向量组成的夹角的大小,从而判断哪个点离圆更近//3、获取最近的点A,计算与局部圆心的长度L//4、与半径比较,若L > radius则不相交,返回//   否则为流形赋值float32 u1 = b2Dot(cLocal - v1, v2 - v1);float32 u2 = b2Dot(cLocal - v2, v1 - v2);// v1 离圆心较近if (u1 <= 0.0f){//不相交,返回if (b2DistanceSquared(cLocal, v1) > radius * radius){return;}//流形初始化manifold->pointCount = 1;manifold->type = b2Manifold::e_faceA;manifold->localNormal = cLocal - v1;manifold->localNormal.Normalize();manifold->localPoint = v1;manifold->points[0].localPoint = circleB->m_p;manifold->points[0].id.key = 0;}//v2离圆心较近else if (u2 <= 0.0f){//不相交,返回if (b2DistanceSquared(cLocal, v2) > radius * radius){return;}//流形初始化manifold->pointCount = 1;manifold->type = b2Manifold::e_faceA;manifold->localNormal = cLocal - v2;manifold->localNormal.Normalize();manifold->localPoint = v2;manifold->points[0].localPoint = circleB->m_p;manifold->points[0].id.key = 0;}//v1与v2离圆心同样远else{//获取两点的中点b2Vec2 faceCenter = 0.5f * (v1 + v2);//计算当前边上的法线在(cLocal - faceCenter)组成向量方向上的投影乘以其长度float32 separation = b2Dot(cLocal - faceCenter, normals[vertIndex1]);//不相交,返回if (separation > radius){return;}//流形初始化manifold->pointCount = 1;manifold->type = b2Manifold::e_faceA;manifold->localNormal = normals[vertIndex1];manifold->localPoint = faceCenter;manifold->points[0].localPoint = circleB->m_p;manifold->points[0].id.key = 0;}
}

对于b2CollideCircles函数主要是获取两圆的圆心距离,同时获取两圆的半径之和,相比较。若圆心距离大于半径和,则没有碰撞,退出;否则碰撞,获得流形。

对于b2CollidePolygonAndCircle函数主要是用于多边形和圆之间的碰撞。主要步骤有:

1、 遍历所有点,获取每个顶点到圆心组成的向量在相应的边上的法向量方向上长度,比较,得到最大的那个长度separation。并获取相应顶点的索引normalIndex

2、 通过normalIndex索引获取对应的边上的两个顶点,同时判断separation是否在容忍误差范围之内,如是则获得流形信息并退出。

3、 计算局部圆心cLocal与顶点组成的向量与多边形边向量之间的点乘,并根据结构判断是否离圆心的远近,然后获得相应流形信息。

此处要用到多边形各个边的法向量。如图4所示:

我们可以看到每条边的法向量是有方向的,也就是说根据我们源码中顶点指向圆心的向量,然后再点乘相应边上的法向量,都到的结果是远离圆的多边形那一半是负的,剩下的一半是正的,进而说明如果他们发生了碰撞,我们求得的最大距离至少不是大于两个形状半径只和的,如图5中:

向量n1与向量AP点乘的结果d1是正的,而向量n2与向量EP点乘的结果d2的结构是负的。大家想像一下,若两形状相撞,必定满足d1<= ra + rb。当然若相撞每一个顶点计算点乘的都应满足此条件。在此我们将获取最大的点乘结果,然后判断是否满足条件。这也是上述1步骤中的一个子步骤。

3、 多边形形状有关的碰撞。即多边形和多边形

此部分主要有4个函数,为避免过长,我们一个一个看。首先看b2EdgeSeparation函数,上源码:

/**************************************************************************
* 功能描述:通过给定的poly1的边缘法向量,在poly1和poly2之间查找间距
* 参数说明: poly1 :多边形1对象指针xf1   :变换1引用edge1 :顶点索引poly2 :多边形2对象指针xf2   :变换2引用
* 返 回 值: 间距值
***************************************************************************/
static float32 b2EdgeSeparation(const b2PolygonShape* poly1, const b2Transform& xf1, int32 edge1,const b2PolygonShape* poly2, const b2Transform& xf2)
{//简单的赋值const b2Vec2* vertices1 = poly1->m_vertices;const b2Vec2* normals1 = poly1->m_normals;int32 count2 = poly2->m_vertexCount;const b2Vec2* vertices2 = poly2->m_vertices;//验证索引的有效性b2Assert(0 <= edge1 && edge1 < poly1->m_vertexCount);// 转换法向量 从poly1的框架到poly2的框架中b2Vec2 normal1World = b2Mul(xf1.q, normals1[edge1]);b2Vec2 normal1 = b2MulT(xf2.q, normal1World);// 查找ploy2上的支撑点int32 index = 0;float32 minDot = b2_maxFloat;//遍历vertices2上的所有点for (int32 i = 0; i < count2; ++i){float32 dot = b2Dot(vertices2[i], normal1);if (dot < minDot){minDot = dot;index = i;}}//计算分离距离b2Vec2 v1 = b2Mul(xf1, vertices1[edge1]);b2Vec2 v2 = b2Mul(xf2, vertices2[index]);float32 separation = b2Dot(v2 - v1, normal1World);return separation;
}

b2EdgeSeparation函数通过给定的poly1的边缘法向量,遍历poly2所有的顶点获取指定法向量和顶点与原点所组成的向量的叉乘,获取最小值,然后计算距离并返回。

再来看看b2FindMaxSeparation函数,还是一样,上源码:

/**************************************************************************
* 功能描述:用poly1上的边缘法向量,在poly1和poly2之间查找最大间距
* 参数说明: edgeIndex:边缘顶点索引,用于接收程序中的输出的最好的边缘点索引poly1    :多边形1对象指针xf1      :变换1引用poly2    :多边形2对象指针xf2      :变换2引用
* 返 回 值:最大间距
***************************************************************************/
static float32 b2FindMaxSeparation(int32* edgeIndex,const b2PolygonShape* poly1, const b2Transform& xf1,const b2PolygonShape* poly2, const b2Transform& xf2)
{int32 count1 = poly1->m_vertexCount;const b2Vec2* normals1 = poly1->m_normals;//获取从poly1质心到poly2质心的向量b2Vec2 d = b2Mul(xf2, poly2->m_centroid) - b2Mul(xf1, poly1->m_centroid);b2Vec2 dLocal1 = b2MulT(xf1.q, d);// 查找poly1的边缘法向量,拥有最大的投影到dint32 edge = 0;float32 maxDot = -b2_maxFloat;// 遍历多边形所有的边,获得其当前质心向量在每条边上的法向量方向上的投影长度。//并比较,获取最大投影长度和相应边的索引for (int32 i = 0; i < count1; ++i){float32 dot = b2Dot(normals1[i], dLocal1);if (dot > maxDot){maxDot = dot;edge = i;}}// 通过边缘法向量获取间距float32 s = b2EdgeSeparation(poly1, xf1, edge, poly2, xf2);//通过前一个边缘法向量检测间距int32 prevEdge = edge - 1 >= 0 ? edge - 1 : count1 - 1;float32 sPrev = b2EdgeSeparation(poly1, xf1, prevEdge, poly2, xf2);//通过下一个边缘法向量检测间距int32 nextEdge = edge + 1 < count1 ? edge + 1 : 0;float32 sNext = b2EdgeSeparation(poly1, xf1, nextEdge, poly2, xf2);// 查找到最好的边和查找方向int32 bestEdge;float32 bestSeparation;int32 increment;//  获取最大距离,和边的索引if (sPrev > s && sPrev > sNext){increment = -1;bestEdge = prevEdge;bestSeparation = sPrev;}else if (sNext > s){increment = 1;bestEdge = nextEdge;bestSeparation = sNext;}else{*edgeIndex = edge;return s;}// 通过最好的边缘法向量执行一个局部查找for ( ; ; ){if (increment == -1)edge = bestEdge - 1 >= 0 ? bestEdge - 1 : count1 - 1;elseedge = bestEdge + 1 < count1 ? bestEdge + 1 : 0;s = b2EdgeSeparation(poly1, xf1, edge, poly2, xf2);if (s > bestSeparation){bestEdge = edge;bestSeparation = s;}else{break;}}//获取边缘*edgeIndex = bestEdge;return bestSeparation;
}

关于此函数,有以下几步:

  1. 获取多边形1质心到多边形2质心的向量,并通过变换获取当前的质心向量dLocal1
  2. 遍历poly1的边缘法向量,在当前的质心向量上的投影,比较并获取最大投影的值,和当前边的索引edge。
  3. 根据索引edge获取与poly2之间查找间距,同时分别获取edge的上一条边和下一条边与poly2间距。
  4. 取三个间距中的最大值,将其索引放到bestEdge中,然后通过bestEdge索引查找所有边到poly2之间的最大间距。并返回最大间距。

对于取点的问题,如果现在的顶点的索引号已经是最大了,要求去取下一个顶点,我们就取索引号为0的。同理,如果现在的顶点的索引号已经是最小了,要求去取上一个顶点,我们就取索引号是最大的。如上面的源码中,(edge - 1 >= 0 ? edge - 1 : count1 – 1)和(edge + 1 < count1 ? edge + 1 :0)这两句。

同样我们来看看b2FinddIncidentEdge函数。上源码:
/**************************************************************************
* 功能描述: 查找入射边
* 参数说明: c        :边缘顶点索引,用于接收程序中的输出的最好的边缘点索引poly1    :多边形1对象指针xf1      :变换1引用edge1    :参照边的索引poly2    :多边形2对象指针xf2      :变换2引用
* 返 回 值:(void)
***************************************************************************/
static void b2FindIncidentEdge(b2ClipVertex c[2],const b2PolygonShape* poly1, const b2Transform& xf1, int32 edge1,const b2PolygonShape* poly2, const b2Transform& xf2)
{//获取poly1的法线向量const b2Vec2* normals1 = poly1->m_normals;//获取poly2的顶点数、首顶点、法线int32 count2 = poly2->m_vertexCount;const b2Vec2* vertices2 = poly2->m_vertices;const b2Vec2* normals2 = poly2->m_normals;//edgeb2Assert(0 <= edge1 && edge1 < poly1->m_vertexCount);// 在poly2的框架下获取参照边上的法线b2Vec2 normal1 = b2MulT(xf2.q, b2Mul(xf1.q, normals1[edge1]));// 查找poly2上入射边int32 index = 0;float32 minDot = b2_maxFloat;
// 遍历多边形2上所有的顶点,获取多边形2上每条边对应法向量在normal1上的投影。//寻找最小的,同时获得对于的索引for (int32 i = 0; i < count2; ++i){float32 dot = b2Dot(normal1, normals2[i]);if (dot < minDot){minDot = dot;index = i;}}// 为入射边创建剪裁顶点int32 i1 = index;int32 i2 = i1 + 1 < count2 ? i1 + 1 : 0;c[0].v = b2Mul(xf2, vertices2[i1]);c[0].id.cf.indexA = (uint8)edge1;c[0].id.cf.indexB = (uint8)i1;c[0].id.cf.typeA = b2ContactFeature::e_face;c[0].id.cf.typeB = b2ContactFeature::e_vertex;c[1].v = b2Mul(xf2, vertices2[i2]);c[1].id.cf.indexA = (uint8)edge1;c[1].id.cf.indexB = (uint8)i2;c[1].id.cf.typeA = b2ContactFeature::e_face;c[1].id.cf.typeB = b2ContactFeature::e_vertex;
}

对于b2FindIncidentEdge函数,我们主要用于查找入射角,具体请看注释,在此也就不多说了。我们再来看看b2CollidePolygons函数。同样上源码:

/**************************************************************************
* 功能描述: 在A上查找边法向量的最大间距 --如果找到分离轴则返回在B上查找边法向量的最大间距 --如果找到分离轴则返回选择参考边 为 min(minA,minB)查找入射边剪裁法向量点是从1到2
* 参数说明: manifold :流形的指针,用于获得两个多边形碰撞而形成的流形polyA    :多边形A对象指针xfA      :变换A引用polyB    :多边形B对象指针xfB      :变换B引用
* 返 回 值:(void)
***************************************************************************/
void b2CollidePolygons(b2Manifold* manifold,const b2PolygonShape* polyA, const b2Transform& xfA,const b2PolygonShape* polyB, const b2Transform& xfB)
{//将流形顶点数量置空manifold->pointCount = 0;float32 totalRadius = polyA->m_radius + polyB->m_radius;//获取分离轴Aint32 edgeA = 0;float32 separationA = b2FindMaxSeparation(&edgeA, polyA, xfA, polyB, xfB);if (separationA > totalRadius)return;//获取分离轴Bint32 edgeB = 0;float32 separationB = b2FindMaxSeparation(&edgeB, polyB, xfB, polyA, xfA);if (separationB > totalRadius)return;const b2PolygonShape* poly1;   // 参照的多边形const b2PolygonShape* poly2;   // 入射的多边形b2Transform xf1, xf2;int32 edge1;                  // 参照边索引uint8 flip;const float32 k_relativeTol = 0.98f;const float32 k_absoluteTol = 0.001f;//初始化分离轴相关变量if (separationB > k_relativeTol * separationA + k_absoluteTol){poly1 = polyB;poly2 = polyA;xf1 = xfB;xf2 = xfA;edge1 = edgeB;manifold->type = b2Manifold::e_faceB;flip = 1;}else{poly1 = polyA;poly2 = polyB;xf1 = xfA;xf2 = xfB;edge1 = edgeA;manifold->type = b2Manifold::e_faceA;flip = 0;}//获取入射边b2ClipVertex incidentEdge[2];b2FindIncidentEdge(incidentEdge, poly1, xf1, edge1, poly2, xf2);//获取多边形poly1的顶点数量、顶点坐标数组int32 count1 = poly1->m_vertexCount;const b2Vec2* vertices1 = poly1->m_vertices;//获取参照边的两个顶点索引int32 iv1 = edge1;int32 iv2 = edge1 + 1 < count1 ? edge1 + 1 : 0;b2Vec2 v11 = vertices1[iv1];b2Vec2 v12 = vertices1[iv2];//获取局部参照边,并转成单位向量b2Vec2 localTangent = v12 - v11;localTangent.Normalize();//获取局部法向量b2Vec2 localNormal = b2Cross(localTangent, 1.0f);b2Vec2 planePoint = 0.5f * (v11 + v12);//获取法向量b2Vec2 tangent = b2Mul(xf1.q, localTangent);b2Vec2 normal = b2Cross(tangent, 1.0f);v11 = b2Mul(xf1, v11);v12 = b2Mul(xf1, v12);// Face offset.// 获取前面【面对自己的那一面】的偏移量float32 frontOffset = b2Dot(normal, v11);// 侧面偏移量,扩展到凸多面体的外壳层float32 sideOffset1 = -b2Dot(tangent, v11) + totalRadius;float32 sideOffset2 = b2Dot(tangent, v12) + totalRadius;// 剪裁入射的边b2ClipVertex clipPoints1[2];b2ClipVertex clipPoints2[2];int np;// 剪裁盒子的侧面1np = b2ClipSegmentToLine(clipPoints1, incidentEdge, -tangent, sideOffset1, iv1);if (np < 2)return;// 剪裁盒子的另一个侧面1np = b2ClipSegmentToLine(clipPoints2, clipPoints1,  tangent, sideOffset2, iv2);if (np < 2){return;}// 现在clipPoints2包含剪裁的点manifold->localNormal = localNormal;manifold->localPoint = planePoint;int32 pointCount = 0;//遍历接触点 for (int32 i = 0; i < b2_maxManifoldPoints; ++i){//获取两个形状之间的间距,用于检测碰撞float32 separation = b2Dot(normal, clipPoints2[i].v) - frontOffset;// 间距小于等于两个形状之间的半径之和// 注意此处的半径是在分离面上的投射半径// 关于分离轴更多信息,可以参照此处// http://blog.csdn.net/bugrunner/article/details/5727256if (separation <= totalRadius){b2ManifoldPoint* cp = manifold->points + pointCount;cp->localPoint = b2MulT(xf2, clipPoints2[i].v);cp->id = clipPoints2[i].id;if (flip){// 交换特征b2ContactFeature cf = cp->id.cf;cp->id.cf.indexA = cf.indexB;cp->id.cf.indexB = cf.indexA;cp->id.cf.typeA = cf.typeB;cp->id.cf.typeB = cf.typeA;}++pointCount;}}//获取流形的顶点数manifold->pointCount = pointCount;
}

对于b2CollidePolygons函数,主要还是查找分离轴,有以下步骤:

  1. 在A上查找边法向量的最大间距,如果找到分离轴则返回
  2. 在B上查找边法向量的最大间距,如果找到分离轴则返回
  3. 选择参考边 为 min(minA,minB)
  4. 查找入射边
  5. 剪裁,获取流形

Ok,我们再说下上一篇中的一个问题吧,在上一篇中,最后部分红色字体的问题,那个2主要是在2d空间中,我们有两个坐标x和y,此时,我们要对坐标x、y分开处理,并用for循环对其索引的方式访问x和y坐标。然后进行相应的计算。

ps:

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

Box2d源码学习十三b2Collision之碰撞(下) 具体形状间的碰撞的实现相关推荐

  1. Box2d源码学习十二b2Collision之碰撞(上)公共部分的实现

    本系列博客是由扭曲45原创,欢迎转载,转载时注明出处,http://blog.csdn.net/cg0206/article/details/8390560 Box2d中将碰撞部分单独放到几个文件中去 ...

  2. Box2d源码学习一之Box2d简介

    本系列博客是由扭曲45原创,欢迎转载,转载时注明出处,http://blog.csdn.net/cg0206/article/details/8257607 随着智能手机的大量普及,手机的性能也越来越 ...

  3. Box2d源码学习十四TOI之碰撞时间的实现

    本系列博客是由扭曲45原创,欢迎转载,转载时注明出处,http://blog.csdn.net/cg0206/article/details/8441644 TOI全称Time of Impact,中 ...

  4. Box2d源码学习十一GJK之距离的实现

    本系列博客是由扭曲45原创,欢迎转载,转载时注明出处,http://blog.csdn.net/cg0206/article/details/8352227 Box2d中距离是指两个形状最近点之间的距 ...

  5. Box2d源码学习二内存管理之SOA的实现

    本系列博客是由扭曲45原创,欢迎转载,转载时注明出处,http://blog.csdn.net/cg0206/article/details/8258166 SOA,全称small object al ...

  6. JDK11源码学习05 | HashMap类

    JDK11源码学习05 | HashMap类 JDK11源码学习01 | Map接口 JDK11源码学习02 | AbstractMap抽象类 JDK11源码学习03 | Serializable接口 ...

  7. VUE源码学习第一篇--前言

    一.目的 前端技术的发展,现在以vue,react,angular为代表的MVVM模式以成为主流,这三个框架大有三分天下之势.react和angular有facebook与谷歌背书,而vue是以一己之 ...

  8. 三方库源码学习2-Retrofit

    文章目录 一.参考博客 二.Retrofit的介绍 三.什么是动态代理 四. Retrofit.Create()方法 五. ServiceMethod 六. 初看HttpServiceMethod 七 ...

  9. Vuex 4源码学习笔记 - 通过dispatch一步步来掌握Vuex整个数据流(五)

    在上一篇笔记中:Vuex 4源码学习笔记 - Store 构造函数都干了什么(四) 我们通过查看Store 构造函数的源代码可以看到主要做了三件事情: 初始化一些内部变量以外 执行installMod ...

最新文章

  1. 织梦html不能生成,内容预览及生成HTML
  2. 统计特性和概率估计-1 (数学推导与证明)
  3. python安装系统要求_python需要什么系统 | window重装系统教程
  4. python学习系列day4-python基础
  5. object picker 微信小程序_微信小程序 demo分享
  6. 边界条件(求解偏微分方程的边界条件)
  7. 机器学习之——神经网络模型
  8. 查看redis数据_关于 Redis 的一些新特性、使用建议和最佳实践
  9. ML Case Studies(0)
  10. 终极广告拦截者软件——AdGuard
  11. 小米路由器怎么设置无盘服务器,小米路由器怎么设置?
  12. c#操作Excel表格插入行和列代码
  13. YouTube双字幕显示
  14. 创建新用户时的相关缺省设置
  15. C语言 空气质量优良率
  16. 2.4父子进程虚拟地址空间情况
  17. 计算机组装原则与注意事项,计算机安装流程和注意事项
  18. 【Python】P1008 [NOIP1998 普及组] 三连击
  19. 一度智信|拼多多客服售后须知
  20. 爱上c++的第六天(核心课程):继承和多态

热门文章

  1. 测试人员如何在项目中开展测试
  2. 为Visual studio 2008 添加汇编工程模板(原创)
  3. 幼儿使用计算机亮度,选儿童护眼灯小心被广告忽悠,亮度值并非越高越好!
  4. Smart3D集群建模步骤
  5. Assembly 调用的目标发生了异常
  6. 调用方法有抛出异常的解决办法
  7. 魅族手机sim卡无显示无服务器,设备管理器无显示内容怎么处理?
  8. 【开发工具】IDEA-DeBug 调试模式使用
  9. emqttd配置_EMQ(emqttd)的介绍和安装
  10. JAVA虚拟机--JVM