Box2d源码学习<十二>b2Collision之碰撞(上)公共部分的实现
本系列博客是由扭曲45原创,欢迎转载,转载时注明出处,http://blog.csdn.net/cg0206/article/details/8390560
Box2d中将碰撞部分单独放到几个文件中去实现的,它的结构和函数用于计算接触点,距离查询和TOI查询。我们根据这些文件实现功能的不同,我们将该部分分成两个小的部分。它们是:
1、 公共部分的实现
2、 具体形状间的碰撞的实现
我们今天就来看第一部分。关于公共部分,主要包括相关结构体的定义和公共函数的实现。
对于b2Collision.h文件,我们也将它分成以下几个部分:
a)、外部类和常量在头文件的声明
b)、结构体的定义
c)、全局函数的声明
d)、内联函数的实现
好了,我们就依次来看看相关源码。
1、外部类和常量在头文件的声明
//根据流形和提供的变换初始化此结构体。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的接触idb2ContactID 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的接触idb2ContactID 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-Hodgmanint32 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 触碰到edgeBvOut[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变量,并获取outputb2DistanceOutput 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裁剪算法,我们简单的说一下,这种方法也叫逐边裁减法,主要思想是:将多边形的各边先相对于窗口的某一条边界进行裁剪,然后将裁剪结果再与另一条边界进行裁剪,如此重复多次,便可得到最终结果。再来说说我们是怎么做的:
因为此处我们只有两个点,故不需要多次重复裁剪。
b2TestOverlap函数主要是通过判断距离来实现是否重叠的,在此就不多说了。
大家知道上面那个问题的答案了吗?
知道了?!太好了,那我就不说了。嘿嘿。。。
ps:
以上文章仅是一家之言,若有不妥、错误之处,请大家多多指出。同时也希望能与大家多多交流,共同进步。