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裁剪算法,我们简单的说一下,这种方法也叫逐边裁减法,主要思想是:将多边形的各边先相对于窗口的某一条边界进行裁剪,然后将裁剪结果再与另一条边界进行裁剪,如此重复多次,便可得到最终结果。再来说说我们是怎么做的:
- 由函数传入相对与法线的偏移量offset,将法线加偏移量作为边界,将平面分成两部分:可见部分和不可见部分。
- 在获取两个输入点向量在法线方向上的长度,并和边界做比较相减,获取结果。
- 然后判断结果和是否有交点,并保存到数组中。
因为此处我们只有两个点,故不需要多次重复裁剪。
b2TestOverlap函数主要是通过判断距离来实现是否重叠的,在此就不多说了。
大家知道上面那个问题的答案了吗?
知道了?!太好了,那我就不说了。嘿嘿。。。
ps:
以上文章仅是一家之言,若有不妥、错误之处,请大家多多指出。同时也希望能与大家多多交流,共同进步。
- 1楼ybi123昨天 14:25
- 45477714