本系列博客是由扭曲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;
}
// 这里的边是否连接B
if (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, //从边缘形状中获得b2EPAxis
e_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、2
b2Vec2 v1, v2; //顶点1、2
b2Vec2 normal; //法向量
b2Vec2 sideNormal1; //侧面1的法向量
float32 sideOffset1; //侧面1的偏移量
b2Vec2 sideNormal2; //侧面2的法向量
float32 sideOffset2; //侧面2的偏移量
};
// 这个类碰撞是一个边缘形状和一个多边形,告诉计算相邻边
struct b2EPCollider
{
/**************************************************************************
* 功能描述: 检查一个边缘形状和一个多边形的碰撞,获取流形
* 参数说明: manifold:流形的指针
edgeA :边缘形状A的指针
xfA :变换A
ploygonB:多边形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_v0
else 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_v3
else 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框架内获取polygonB
m_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;
// 剪裁盒子的侧面1
np = 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 :变换A
ploygonB:多边形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)
{
//将流形顶点置0
manifold->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的边缘法向量,拥有最大的投影到d
int32 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;
else
edge = 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质心到多边形2质心的向量,并通过变换获取当前的质心向量dLocal1
- 遍历poly1的边缘法向量,在当前的质心向量上的投影,比较并获取最大投影的值,和当前边的索引edge。
- 根据索引edge获取与poly2之间查找间距,同时分别获取edge的上一条边和下一条边与poly2间距。
- 取三个间距中的最大值,将其索引放到bestEdge中,然后通过bestEdge索引查找所有边到poly2之间的最大间距。并返回最大间距。
对于取点的问题,如果现在的顶点的索引号已经是最大了,要求去取下一个顶点,我们就取索引号为0的。同理,如果现在的顶点的索引号已经是最小了,要求去取上一个顶点,我们就取索引号是最大的。如上面的源码中,(edge - 1 >= 0 ? edge - 1 : count1 – 1)和(edge + 1 < count1 ? edge + 1 :0)这两句。
/**************************************************************************
* 功能描述: 查找入射边
* 参数说明: 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;
//edge
b2Assert(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;
//获取分离轴A
int32 edgeA = 0;
float32 separationA = b2FindMaxSeparation(&edgeA, polyA, xfA, polyB, xfB);
if (separationA > totalRadius)
return;
//获取分离轴B
int32 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;
// 剪裁盒子的侧面1
np = b2ClipSegmentToLine(clipPoints1, incidentEdge, -tangent, sideOffset1, iv1);
if (np < 2)
return;
// 剪裁盒子的另一个侧面1
np = 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/5727256
if (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函数,主要还是查找分离轴,有以下步骤:
- 在A上查找边法向量的最大间距,如果找到分离轴则返回
- 在B上查找边法向量的最大间距,如果找到分离轴则返回
- 选择参考边 为 min(minA,minB)
- 查找入射边
- 剪裁,获取流形
Ok,我们再说下上一篇中的一个问题吧,在上一篇中,最后部分红色字体的问题,那个2主要是在2d空间中,我们有两个坐标x和y,此时,我们要对坐标x、y分开处理,并用for循环对其索引的方式访问x和y坐标。然后进行相应的计算。
ps:
以上文章仅是一家之言,若有不妥、错误之处,请大家多多指出。同时也希望能与大家多多交流,共同进步。