首页 > 代码库 > 一个不靠谱的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)