码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2015-01-29 19:14:45      阅读:170      评论:0      收藏:0      [点我收藏+]

标签:

主要参考: 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)

标签:

原文地址:http://www.cnblogs.com/pngx/p/4260622.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!