privatestaticvoidProject(ConvexPolygon poly, Vector2 axis, outfloat min, outfloat max) { var projection = Vector2.Dot(poly.Vertices[0], axis); min = projection; max = projection;
for (var i = 1; i < poly.Vertices.Length; i++) { projection = Vector2.Dot(poly.Vertices[i], axis);
if (projection < min) min = projection; if (projection > max) max = projection; } }
privatestatic Vector2 ComputeCenter(ConvexPolygon poly) { var x = 0f; var y = 0f; var length = poly.Vertices.Length; for (var i = 0; i < poly.Vertices.Length; i++) { x += poly.Vertices[i].x; y += poly.Vertices[i].y; } returnnew Vector2(x / length, y / length); } }
publicstaticclassGjk { privatestaticreadonly List<Vector2> Simplex = new(); publicstaticboolIntersects(ConvexPolygon a, ConvexPolygon b) { Simplex.Clear(); var d = Vector2.right;
Simplex.Add(Support(a, b, d)); d = -Simplex[0];
while (true) { var p = Support(a, b, d);
if (Vector2.Dot(p, d) <= 0) returnfalse; // 不相交
Simplex.Add(p);
if (HandleSimplex(Simplex, ref d)) returntrue; // 相交 } }
// 2D 只有两种情况: // 当前 simplex 是线段 // 当前 simplex 是三角形 publicstaticboolHandleSimplex(List<Vector2> simplex, ref Vector2 d) { if (simplex.Count == 2) { // simplex: [B, A](注意:最后加入的是 A) var a = simplex[1]; var b = simplex[0];
var ab = b - a; var ao = -a;
// AB 垂直方向,指向原点那边 d = TripleProduct(ab, ao, ab); // 防止出现共线情况(两个正方形紧邻,交集是一条线) if (d.sqrMagnitude < 1e-6f) { d = new Vector2(-ab.y, ab.x); // 这里主要是防止浮点数误差,对于数学上严格共线的情况下,点积必然为0,法向量方向不用在意 if (Vector2.Dot(ao, d) < 0) d = -d; }
returnfalse; } else { // simplex: [C, B, A](注意:最后加入的是 A) var a = simplex[2]; var b = simplex[1]; var c = simplex[0];
var ab = b - a; var ac = c - a; var ao = -a;
var abPerp = TripleProduct(ac, ab, ab); var acPerp = TripleProduct(ab, ac, ac);
// 原点在 AB 外侧 if (Vector2.Dot(abPerp, ao) > 0) { simplex.RemoveAt(0); // 移除 C d = abPerp; returnfalse; }
// 原点在 AC 外侧 if (Vector2.Dot(acPerp, ao) > 0) { simplex.RemoveAt(1); // 移除 B d = acPerp; returnfalse; }
// 原点在三角形内部 returntrue; } }
// 向量三重积公式来求取向量A向原点的法向量 // aXbXc结果是c的垂线(与(b-a)点积大于0的方向) publicstatic Vector2 TripleProduct(Vector2 a, Vector2 b, Vector2 c) { var ac = Vector2.Dot(a, c); var bc = Vector2.Dot(b, c); return b * ac - a * bc; } }
publicstaticboolIntersects(ConvexPolygon a, ConvexPolygon b, out Vector2 mtv) { mtv = default; Simplex.Clear();
if (!GjkInternal(a, b, Simplex)) returnfalse;
// 进入 EPA mtv = EPA(a, b, Simplex); returntrue; }
#region GJK privatestaticboolGjkInternal(ConvexPolygon a, ConvexPolygon b, List<Vector2> simplex) { var d = Vector2.right;
simplex.Add(Gjk.Support(a, b, d)); d = -simplex[0];
while (true) { var p = Gjk.Support(a, b, d);
if (Vector2.Dot(p, d) <= 0) returnfalse;
simplex.Add(p);
if (Gjk.HandleSimplex(simplex, ref d)) returntrue; } } #endregion
#region EPA privatestatic Vector2 EPA(ConvexPolygon A, ConvexPolygon B, List<Vector2> simplex) { // 包含三个顶点 var polytope = new List<Vector2>(simplex); for (var iter = 0; iter < MaxIterations; iter++) { var closestEdgeIndex = -1; var minDist = float.MaxValue; var closestNormal = Vector2.zero;
// 找离原点最近的边 for (var i = 0; i < polytope.Count; i++) { var j = (i + 1) % polytope.Count; var a = polytope[i]; var b = polytope[j]; var edge = b - a; var toOrigin = -a;
var normal = Gjk.TripleProduct(edge, toOrigin, edge).normalized; // 指向原点 if (normal.sqrMagnitude < Epsilon) { normal = new Vector2(a.y, -a.x); if (Vector2.Dot(normal, toOrigin) < 0) normal = -normal; normal = normal.normalized; } var dist = Mathf.Abs(Vector2.Dot(normal, a));