首页 > 代码库 > 一个不靠谱的2D物理引擎(4) - 裁剪出碰撞点(manifold)

一个不靠谱的2D物理引擎(4) - 裁剪出碰撞点(manifold)

主要参考: http://www.codezealot.org/archives/394 , 建议阅读

 

第一篇已经解决了如何判断两个图形是否相交以及求嵌入深度. 之后还需要求两物体的接触点.

实际上物体的接触部分可以是一个面, 叫做manifold. 2D下就是一条线段.

 

对于两个多边形的碰撞, 首先要找出两个多边形相互离得最近的两条边. 在第一篇的修改版的getAxisLeastPenetration函数已经完成了这个工作. 把polyA和polyB相互交换作为该函数的参数执行2次, 取distance最大的那个作为结果.

getAxisLeastPenetration: function (oPolyA, oPolyB) {                var normal,                    supportIndex,                    distance,                    bestDistance = -Infinity,                    bestNormal,                    refIndex,                    incIndex,                    len = oPolyA.length;                for (var i = 0; i < len; ++i) {                    normal = oPolyA.getEdgeNormal(i);                    supportIndex = oPolyB.getFarthestPointIndex(normal.negate());                    distance = oPolyB[supportIndex].sub(oPolyA[i]).dot(normal);                    if (distance > bestDistance) {                        bestDistance = distance;                        bestNormal = normal;                        refIndex = i;                        incIndex = supportIndex;                    }                }

之后需要找出"参考边(ref)"和"事件边(inc)".

参考边就是两条边中与碰撞方向向量normal最垂直的那条边. 这里直接取normal对应的那条边当参考边就可以了.

对于事件边, 上面的函数只返回了离得最近的顶点, 要从这个顶点相邻的两条边里选一条当事件边. 这里取与normal向量更垂直的那条边.

getIncidentEdgeIndex: function (oPoly, nIndex, oNormal) {                var vecLf = oPoly.getEdgeVector(nIndex - 1).normalize(),                    vecRt = oPoly.getEdgeVector(nIndex).normalize(),                    cosLf = Math.abs(vecLf.dot(oNormal)),                    cosRt = Math.abs(vecRt.dot(oNormal));                return cosLf < cosRt ? nIndex - 1 : nIndex;            }

 

getEdgeVector: function (nIndex) {                var len = this.length;                if (nIndex < 0) {                    nIndex += len;                }                return this[(nIndex + 1) % len].sub(this[nIndex]);            }

 

                axisAB = P.Polygon.getAxisLeastPenetration(polyA, polyB);                if (axisAB.distance >= 0) {                    return null;                }                axisBA = P.Polygon.getAxisLeastPenetration(polyB, polyA);                if (axisBA.distance >= 0) {                    return null;                }                if (axisAB.distance > axisBA.distance) {                    manifold.bodyA = this._bodyA;                    manifold.bodyB = this._bodyB;                    manifold.penetration = -axisAB.distance;                    refPoly = polyA;                    incPoly = polyB;                    axisNormal = axisAB.normal;                    refEdgeIndex = axisAB.refEdgeIndex;                    incEdgeIndex = C.getIncidentEdgeIndex(incPoly, axisAB.incVertexIndex, axisNormal);                } else {                    manifold.bodyA = this._bodyB;                    manifold.bodyB = this._bodyA;                    manifold.penetration = -axisBA.distance;                    refPoly = polyB;                    incPoly = polyA;                    axisNormal = axisBA.normal;                    refEdgeIndex = axisBA.refEdgeIndex;                    incEdgeIndex = C.getIncidentEdgeIndex(incPoly, axisBA.incVertexIndex, axisNormal);                }

 

然后要用参考边对事件边进行裁剪, 也就是把事件边嵌入在参考多边形的那一部分给裁剪出来.

这里借助一个裁剪向量的函数, 输入一条被裁剪边, 一个裁剪方向, 这个函数把边在裁剪方向向量上的投影比min小的部分全部截去.

clipEdge: function (aEdge, oDir, oMin) {                var min = oMin.dot(oDir),                    p1 = aEdge[0].dot(oDir) - min,                    p2 = aEdge[1].dot(oDir) - min,                    ret = [];                if (p1 >= 0) {                    ret.push(aEdge[0]);                }                if (p2 >= 0) {                    ret.push(aEdge[1]);                }                if (p1 * p2 < 0) {                    var e = aEdge[1].sub(aEdge[0]);                    e = e.mul(p1 / (p1 - p2)).add(aEdge[0]);                    ret.push(e);                }                return ret;            }

然后把事件边在参考边外侧的(参考多边形外面的), 左侧的, 右侧的部分全部裁剪掉, 剩下的就是manifold.

                refEdge = refPoly.getEdge(refEdgeIndex);                incEdge = incPoly.getEdge(incEdgeIndex);                clipDirection = axisNormal.vertical(true);                clipEdge = C.clipEdge(incEdge, clipDirection, refEdge[0]);                if (clipEdge.length < 2) {                    return null;                }                clipDirection = axisNormal.vertical(false);                clipEdge = C.clipEdge(clipEdge, clipDirection, refEdge[1]);                if (clipEdge.length < 2) {                    return null;                }                clipDirection = axisNormal.negate();                clipEdge = C.clipEdge(clipEdge, clipDirection, refEdge[0]);                if (clipEdge.length < 2) {                    return null;                }                manifold.contact = clipEdge;                manifold.normal = axisNormal;                return manifold;

最后整个求多边形间manifold的函数如下:

solvePolygon: function (polyA, polyB) {                var manifold = new Manifold(),                    axisAB,                    axisBA,                    axisNormal,                    refEdgeIndex,                    incEdgeIndex,                    refPoly,                    incPoly,                    refEdge,                    incEdge,                    clipDirection,                    clipEdge;                axisAB = getAxisLeastPenetration(polyA, polyB);                if (axisAB.distance >= 0) {                    return null;                }                axisBA = getAxisLeastPenetration(polyB, polyA);                if (axisBA.distance >= 0) {                    return null;                }                if (axisAB.distance > axisBA.distance) {                    manifold.bodyA = this._bodyA;                    manifold.bodyB = this._bodyB;                    manifold.penetration = -axisAB.distance;                    refPoly = polyA;                    incPoly = polyB;                    axisNormal = axisAB.normal;                    refEdgeIndex = axisAB.refEdgeIndex;                    incEdgeIndex = getIncidentEdgeIndex(incPoly, axisAB.incVertexIndex, axisNormal);                } else {                    manifold.bodyA = this._bodyB;                    manifold.bodyB = this._bodyA;                    manifold.penetration = -axisBA.distance;                    refPoly = polyB;                    incPoly = polyA;                    axisNormal = axisBA.normal;                    refEdgeIndex = axisBA.refEdgeIndex;                    incEdgeIndex = getIncidentEdgeIndex(incPoly, axisBA.incVertexIndex, axisNormal);                }                refEdge = refPoly.getEdge(refEdgeIndex);                incEdge = incPoly.getEdge(incEdgeIndex);                clipDirection = axisNormal.vertical(true);                clipEdge = clipEdge(incEdge, clipDirection, refEdge[0]);                if (clipEdge.length < 2) {                    return null;                }                clipDirection = axisNormal.vertical(false);                clipEdge = clipEdge(clipEdge, clipDirection, refEdge[1]);                if (clipEdge.length < 2) {                    return null;                }                clipDirection = axisNormal.negate();                clipEdge = clipEdge(clipEdge, clipDirection, refEdge[0]);                if (clipEdge.length < 2) {                    return null;                }                manifold.contact = clipEdge;                manifold.normal = axisNormal;                return manifold;            }

 

求两个圆之间的manifold只要取两圆心连线中点即可, 这个manifold只有一个点.

solveCircle: function (cirA, cirB) {                var manifold = new Manifold(),                    pA = cirA.center,                    pB = cirB.center,                    rA = cirA.radius,                    rB = cirB.radius,                    normal = pB.sub(pA),                    distance = normal.getLength();                if (distance >= rA + rB) {                    return null;                }                manifold.bodyA = this._bodyA;                manifold.bodyB = this._bodyB;                manifold.normal = normal.normalize();                manifold.penetration = rA + rB - distance;                manifold.contact = [pA.add(pB).mul(0.5)];                return manifold;            }

 

多边形与圆的情况(注释可以参照第一篇):

solvePolygonCircle: function () {                var manifold = new P.Manifold(),                    poly = this._bodyA.shape.graph,                    len = poly.length,                    circle = this._bodyB.shape.graph,                    center = circle.center,                    r = circle.radius;                var distance,                    bestDistance = -Infinity,                    edgeIndex;                for (var i = len; i--;) {                    distance = poly.getEdgeNormal(i).dot(center.sub(poly[i]));                    if (distance > r) {                        return null;                    }                    if (distance > bestDistance) {                        bestDistance = distance;                        edgeIndex = i;                    }                }                manifold.bodyA = this._bodyA;                manifold.bodyB = this._bodyB;                if (distance < 0) {                    manifold.normal = poly.getEdgeNormal(edgeIndex);                    manifold.contact = [manifold.normal.mul(r).negate().add(center)];                    manifold.penetration = r;                }                var edge = poly.getEdge(edgeIndex);                manifold.penetration = r - bestDistance;                var cv1 = center.sub(edge[0]),                    cos1 = cv1.dot(edge[1].sub(edge[0]));                if (cos1 <= 0) {                    if (cv1.dot(cv1) > r * r) {                        return null;                    }                    manifold.normal = cv1.normalize();                    manifold.contact = [edge[0]];                    return manifold;                }                var cv2 = center.sub(edge[1]),                    cos2 = cv2.dot(edge[0].sub(edge[1]));                if (cos2 <= 0) {                    if (cv2.dot(cv2) > r * r) {                        return null;                    }                    manifold.normal = cv2.normalize();                    manifold.contact = [edge[1]];                    return manifold;                }                var normal = poly.getEdgeNormal(edgeIndex);                if(edge[0].sub(center).dot(normal) > r) {                    return null;                }                manifold.normal = normal;                manifold.contact = [normal.mul(r).add(center)];                return manifold;            }

 

一个不靠谱的2D物理引擎(4) - 裁剪出碰撞点(manifold)