AABB/三角形相交时Y轴角落MTV计算错误,如何修复?
AABB与三角形相交的MTV计算Y轴角落结果错误的修复方案
我在计算AABB(轴对齐包围盒)与三角形相交的MTV(最小平移向量)时,Y轴角落处的结果不正确。
当前结果:
以下是原代码:
public static bool BoxTriangleIntersection(Box box, Triangle triangle, out Vector3 minTranslation) { minTranslation = Vector3.Zero; var boxCenter = box.GetCenter(); var boxExtents = box.GetHalfExtent(); var aPointRelativeToBox = triangle[0] - boxCenter; var bPointRelativeToBox = triangle[1] - boxCenter; var cPointRelativeToBox = triangle[2] - boxCenter; var triangleEdges = new[] { triangle[1] - triangle[0], triangle[2] - triangle[1], triangle[0] - triangle[2] }; for (var i = 0; i < 3; i++) { var projectionAxis = new Vector3(0, -triangleEdges[i].Z, triangleEdges[i].Y); var testPointA = Vector3.Dot(aPointRelativeToBox, projectionAxis); var testPointB = Vector3.Dot(bPointRelativeToBox, projectionAxis); var testPointC = Vector3.Dot(cPointRelativeToBox, projectionAxis); var radius = boxExtents.Y * Math.Abs(triangleEdges[i].Z) + boxExtents.Z * Math.Abs(triangleEdges[i].Y); if (Math.Max(-MathBindings.Max(testPointA, testPointB, testPointC), MathBindings.Min(testPointA, testPointB, testPointC)) > radius) { return false; } } for (var i = 0; i < 3; i++) { var projectionAxis = new Vector3(triangleEdges[i].Z, 0, -triangleEdges[i].X); var testPointA = Vector3.Dot(aPointRelativeToBox, projectionAxis); var testPointB = Vector3.Dot(bPointRelativeToBox, projectionAxis); var testPointC = Vector3.Dot(cPointRelativeToBox, projectionAxis); var radius = boxExtents.X * Math.Abs(triangleEdges[i].Z) + boxExtents.Z * Math.Abs(triangleEdges[i].X); if (Math.Max(-MathBindings.Max(testPointA, testPointB, testPointC), MathBindings.Min(testPointA, testPointB, testPointC)) > radius) { return false; } } for (var i = 0; i < 3; i++) { var projectionAxis = new Vector3(-triangleEdges[i].Y, triangleEdges[i].X, 0); var testPointA = Vector3.Dot(aPointRelativeToBox, projectionAxis); var testPointB = Vector3.Dot(bPointRelativeToBox, projectionAxis); var testPointC = Vector3.Dot(cPointRelativeToBox, projectionAxis); var radius = boxExtents.X * Math.Abs(triangleEdges[i].Y) + boxExtents.Y * Math.Abs(triangleEdges[i].X); if (Math.Max(-MathBindings.Max(testPointA, testPointB, testPointC), MathBindings.Min(testPointA, testPointB, testPointC)) > radius) { return false; } } for (var i = 0; i < 3; i++) { if (MathBindings.Max(aPointRelativeToBox[i], bPointRelativeToBox[i], cPointRelativeToBox[i]) < -boxExtents[i] || MathBindings.Min(aPointRelativeToBox[i], bPointRelativeToBox[i], cPointRelativeToBox[i]) > boxExtents[i]) { return false; } } var planeNormal = triangle.GetNormal().Normalized(); var planeDistance = Math.Abs(Vector3.Dot(planeNormal, aPointRelativeToBox)); var projectionIntervalRadius = boxExtents.X * Math.Abs(planeNormal.X) + boxExtents.Y * Math.Abs(planeNormal.Y) + boxExtents.Z * Math.Abs(planeNormal.Z); minTranslation = planeNormal * (projectionIntervalRadius - planeDistance); return planeDistance <= projectionIntervalRadius; }
问题根源
原代码仅使用三角形平面法线方向计算MTV,但SAT(分离轴定理)要求检查所有潜在分离轴,并选择重叠量最小的轴作为MTV方向。Y轴角落的碰撞中,最小重叠往往不在三角形法线方向,而是在AABB自身轴或三角形边与AABB边的叉乘轴上,原代码忽略了这些轴的重叠计算,导致结果错误。
修复方案
核心修改点
- 跟踪所有分离轴的重叠量,保留最小重叠对应的平移向量
- 归一化所有分离轴,避免长度不一致导致的计算误差
- 修正各轴的重叠量与平移方向计算逻辑
修改后的代码
public static bool BoxTriangleIntersection(Box box, Triangle triangle, out Vector3 minTranslation) { minTranslation = Vector3.Zero; float minOverlap = float.MaxValue; var boxCenter = box.GetCenter(); var boxExtents = box.GetHalfExtent(); var a = triangle[0] - boxCenter; var b = triangle[1] - boxCenter; var c = triangle[2] - boxCenter; var edges = new[] { triangle[1] - triangle[0], triangle[2] - triangle[1], triangle[0] - triangle[2] }; // 分离轴检测辅助方法:计算重叠量并更新最小MTV void TestAxis(Vector3 axis) { axis = axis.Normalized(); if (axis == Vector3.Zero) return; // 计算三角形在轴上的投影区间 float triA = Vector3.Dot(a, axis); float triB = Vector3.Dot(b, axis); float triC = Vector3.Dot(c, axis); float triMin = MathBindings.Min(triA, triB, triC); float triMax = MathBindings.Max(triA, triB, triC); // 计算AABB在轴上的投影区间(相对于中心,范围是[-radius, radius]) float radius = boxExtents.X * Math.Abs(axis.X) + boxExtents.Y * Math.Abs(axis.Y) + boxExtents.Z * Math.Abs(axis.Z); // 检测是否分离,分离则直接终止检测 if (triMax < -radius || triMin > radius) { throw new InvalidOperationException("Separated"); } // 计算重叠量与平移方向 float overlap; Vector3 direction; if (triMin < -radius) { // 三角形在AABB负方向外侧,向正方向平移 overlap = triMax - (-radius); direction = axis; } else if (triMax > radius) { // 三角形在AABB正方向外侧,向负方向平移 overlap = radius - triMin; direction = -axis; } else { // 区间包含,取最近的方向 float distToNeg = triMin - (-radius); float distToPos = radius - triMax; if (distToNeg < distToPos) { overlap = distToNeg; direction = -axis; } else { overlap = distToPos; direction = axis; } } // 更新最小平移向量 if (overlap < minOverlap) { minOverlap = overlap; minTranslation = direction * overlap; } } try { // 检测三角形边与AABB轴的叉乘轴(共9个) foreach (var edge in edges) { TestAxis(new Vector3(0, -edge.Z, edge.Y)); TestAxis(new Vector3(edge.Z, 0, -edge.X)); TestAxis(new Vector3(-edge.Y, edge.X, 0)); } // 检测AABB自身的三个轴 TestAxis(Vector3.UnitX); TestAxis(Vector3.UnitY); TestAxis(Vector3.UnitZ); // 检测三角形平面法线轴 var planeNormal = triangle.GetNormal().Normalized(); TestAxis(planeNormal); } catch (InvalidOperationException) { minTranslation = Vector3.Zero; return false; } return true; }
关键修复说明
- 归一化分离轴:确保不同轴的重叠量可以直接比较,避免轴长度差异导致的计算偏差
- 全轴检测:覆盖SAT要求的所有潜在分离轴(三角形边叉乘AABB轴、AABB自身轴、三角形法线轴)
- 动态跟踪最小MTV:在每个轴检测时计算对应的平移向量,保留长度最小的结果,确保得到真正的最小平移向量
内容的提问来源于stack exchange,提问作者Rede Boy
相关产品推荐
相关产品推荐

